diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index ec4e2b48e2..df6367b8b2 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -89,7 +89,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_chat_status_members_online" = "{count:_not_used_|# member|# members}, {count_online:_not_used_|# online|# online}"; "lng_server_error" = "Internal server error."; -"lng_flood_error" = "Too much tries. Please try again later."; +"lng_flood_error" = "Too many tries. Please try again later."; "lng_deleted" = "Unknown"; "lng_deleted_message" = "Deleted message"; @@ -127,16 +127,21 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_signin_title" = "Cloud password check"; "lng_signin_desc" = "Please enter your cloud password."; -"lng_signin_recover_desc" = "Please enter code from recovery email."; +"lng_signin_recover_desc" = "Please enter the code from the e-mail."; "lng_signin_password" = "Your cloud password"; -"lng_signin_code" = "Code from email"; -"lng_signin_recover" = "Recover by email"; +"lng_signin_code" = "Code from e-mail"; +"lng_signin_recover" = "Forgot password?"; "lng_signin_hint" = "Hint: {password_hint}"; -"lng_signin_recover_hint" = "Code was sent to {recover_email}."; +"lng_signin_recover_hint" = "Code was sent to {recover_email}"; "lng_signin_bad_password" = "You have entered a wrong password."; "lng_signin_wrong_code" = "You have entered an invalid code. Please try again."; -"lng_signin_try_password" = "I remember the password"; -"lng_signin_password_removed" = "Your cloud password was removed.\nYou can set new one in Settings."; +"lng_signin_try_password" = "Having trouble accessing your e-mail?"; +"lng_signin_password_removed" = "Your cloud password was disabled.\nYou can set a new one in Settings."; +"lng_signin_no_email_forgot" = "Since you haven't provided a recovery\ne-mail when setting up your password, your remaining options are either to remember your password or to reset your account."; +"lng_signin_cant_email_forgot" = "If you can't restore access to the e-mail, your remaining options are either to remember your password or to reset your account."; +"lng_signin_reset_account" = "Reset your account"; +"lng_sigin_sure_reset" = "Warning!\n\nYou will lose all your chats and messages,\nalong with any media and files you shared!\n\nDo you want to reset your account?"; +"lng_sigin_reset" = "Reset"; "lng_signup_title" = "Information and photo"; "lng_signup_desc" = "Please enter your name and\nupload a photo."; @@ -266,27 +271,28 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_passcode_submit" = "Submit"; "lng_passcode_logout" = "Log out"; -"lng_cloud_password_waiting" = "Confirmation sent to {email}.."; +"lng_cloud_password_waiting" = "Confirmation link sent to {email}.."; "lng_cloud_password_change" = "Change cloud password"; "lng_cloud_password_create" = "Create cloud password"; "lng_cloud_password_remove" = "Remove cloud password"; -"lng_cloud_password_set" = "Turn on cloud password"; +"lng_cloud_password_set" = "Enable two-step verification"; "lng_cloud_password_edit" = "Change cloud password"; "lng_cloud_password_enter_old" = "Enter old password"; "lng_cloud_password_enter_new" = "Enter new password"; "lng_cloud_password_confirm_new" = "Re-enter new password"; -"lng_cloud_password_hint" = "Enter new password hint"; -"lng_cloud_password_bad" = "Password and hint should differ."; -"lng_cloud_password_email" = "Enter recover email"; -"lng_cloud_password_bad_email" = "Incorrect email, please try other."; -"lng_cloud_password_about" = "Each new device authorization will require entering cloud password or recover by email."; -"lng_cloud_password_about_recover" = "Warning! Are you sure you don't want to\nenter a recover email address?\n\nIf you forget your cloud password\nyou will loose all your account data."; -"lng_cloud_password_almost" = "A confirmation link was sent\nto the email you provided.\n\nYour cloud password will be enabled\nonly after you follow that link."; -"lng_cloud_password_was_set" = "Your new password is enabled."; -"lng_cloud_password_removed" = "Your cloud password was removed."; -"lng_cloud_password_differ" = "Passwords are different"; +"lng_cloud_password_hint" = "Enter password hint"; +"lng_cloud_password_bad" = "Password and hint cannot be the same."; +"lng_cloud_password_email" = "Enter recovery e-mail"; +"lng_cloud_password_bad_email" = "Incorrect e-mail, please try other."; +"lng_cloud_password_about" = "This password will be required when you log in on a new device in addition to the pin code."; +"lng_cloud_password_about_recover" = "Warning! Are you sure you don't want to\nadd a password recovery e-mail?\n\nIf you forget your password, you will\nlose access to your Telegram account."; +"lng_cloud_password_almost" = "A confirmation link was sent\nto the e-mail you provided.\n\nTwo-step verification will be enabled\nas soon as you follow that link."; +"lng_cloud_password_was_set" = "Two-step verification enabled."; +"lng_cloud_password_updated" = "Your cloud password was updated."; +"lng_cloud_password_removed" = "Two-step verification was disabled."; +"lng_cloud_password_differ" = "Passwords do not match"; "lng_cloud_password_wrong" = "Wrong cloud password"; -"lng_cloud_password_is_same" = "Cloud password was not changed"; +"lng_cloud_password_is_same" = "Password was not changed"; "lng_connection_type" = "Connection type:"; "lng_connection_auto_connecting" = "Default (connecting..)"; @@ -531,7 +537,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_mediaview_saved" = "Image was saved to your [c]Downloads[/c] folder"; -"lng_new_authorization" = "{name},\nWe detected a login into your account from a new device on {day}, {date} at {time}\n\nDevice: {device}\nLocation: {location}\n\nIf this wasn't you, you can go to Settings — Show all sessions and terminate the new session there.\n\nThanks,\nThe Telegram Team"; +"lng_new_authorization" = "{name},\nWe detected a login into your account from a new device on {day}, {date} at {time}\n\nDevice: {device}\nLocation: {location}\n\nIf this wasn't you, you can go to Settings — Show all sessions and terminate that session.\n\nIf you think that somebody logged in to your account against your will, you can enable two-step verification in Settings.\n\nSincerely,\nThe Telegram Team"; "lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}"; "lng_new_version_minor" = "— Bug fixes and other minor improvements"; diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index feded50ff4..fbc1ea404e 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -458,6 +458,16 @@ btnSelectCancel: flatButton(btnSelectDone) { downColor: btnNoHover; } btnSelectSep: #e0e0e0; +btnRedLink: linkButton(btnDefLink) { + color: #d15948; + overColor: #d15948; + downColor: #db6352; +} +btnRedDone: flatButton(btnSelectDone) { + color: #d15948; + overColor: #d15948; + downColor: #db6352; +} countryList: countryList { notFoundColor: #aaa;//rgb(20, 136, 210); @@ -825,6 +835,7 @@ msgBG: ':/gui/art/bg.png' / 2:':/gui/art/bg_125x.png' / 3:':/gui/art/bg_150x.png msgSendingRect: sprite(260px, 20px, 20px, 20px); msgCheckRect: sprite(320px, 0px, 20px, 20px); +msgCheckPos: point(5px, 1px); msgDblCheckRect: sprite(300px, 0px, 20px, 20px); msgSelectCheckRect: sprite(160px, 0px, 20px, 20px); msgSelectDblCheckRect: sprite(140px, 0px, 20px, 20px); @@ -1379,15 +1390,15 @@ dropdownMediaPhotos: iconedButton(dropdownAttachPhoto) { width: 200px; } dropdownMediaVideos: iconedButton(dropdownMediaPhotos) { - icon: sprite(79px, 348px, 24px, 24px); - downIcon: sprite(79px, 348px, 24px, 24px); + icon: sprite(92px, 348px, 24px, 24px); + downIcon: sprite(92px, 348px, 24px, 24px); } dropdownMediaDocuments: iconedButton(dropdownAttachDocument) { width: 200px; } dropdownMediaAudios: iconedButton(dropdownMediaDocuments) { - icon: sprite(49px, 348px, 24px, 24px); - downIcon: sprite(49px, 348px, 24px, 24px); + icon: sprite(62px, 348px, 24px, 24px); + downIcon: sprite(62px, 348px, 24px, 24px); } dragFont: font(28px semibold); @@ -1721,6 +1732,7 @@ usernameCancel: flatButton(btnSelectCancel) { youtubeIcon: sprite(336px, 221px, 60px, 60px); vimeoIcon: sprite(336px, 283px, 60px, 60px); +videoIcon: sprite(0px, 340px, 60px, 60px); locationSize: size(320, 240); langsWidth: 220px; @@ -1779,3 +1791,11 @@ sessionTerminate: iconedButton(notifyClose) { width: 16px; height: 16px; } + +webPageLeft: 10px; +webPageBar: 2px; +webPageTitleFont: font(fsize semibold); +webPageDescriptionFont: font(fsize); +webPagePhotoSkip: 5px; +webPagePhotoSize: 100px; +webPagePhotoDelta: 8px; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 240d9106df..106aef8440 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -30,6 +30,7 @@ ApiWrap::ApiWrap(QObject *parent) : QObject(parent) { App::initBackground(); connect(&_replyToTimer, SIGNAL(timeout()), this, SLOT(resolveReplyTo())); + connect(&_webPagesTimer, SIGNAL(timeout()), this, SLOT(resolveWebPages())); } void ApiWrap::init() { @@ -94,6 +95,25 @@ void ApiWrap::requestFullPeer(PeerData *peer) { _fullRequests.insert(peer, req); } +void ApiWrap::requestWebPageDelayed(WebPageData *page) { + if (page->pendingTill <= 0) return; + _webPagesPending.insert(page, 0); + int32 left = (page->pendingTill - unixtime()) * 1000; + if (!_webPagesTimer.isActive() || left <= _webPagesTimer.remainingTime()) { + _webPagesTimer.start((left < 0 ? 0 : left) + 1); + } +} + +void ApiWrap::clearWebPageRequest(WebPageData *page) { + _webPagesPending.remove(page); + if (_webPagesPending.isEmpty() && _webPagesTimer.isActive()) _webPagesTimer.stop(); +} + +void ApiWrap::clearWebPageRequests() { + _webPagesPending.clear(); + _webPagesTimer.stop(); +} + void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result) { const MTPDmessages_chatFull &d(result.c_messages_chatFull()); App::feedUsers(d.vusers); @@ -123,7 +143,9 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result) { emit fullPeerLoaded(peer); } -bool ApiWrap::gotPeerFailed(PeerData *peer, const RPCError &err) { +bool ApiWrap::gotPeerFailed(PeerData *peer, const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + _fullRequests.remove(peer); return true; } @@ -146,6 +168,34 @@ void ApiWrap::resolveReplyTo() { } } +void ApiWrap::resolveWebPages() { + QVector ids; + const WebPageItems &items(App::webPageItems()); + ids.reserve(_webPagesPending.size()); + int32 t = unixtime(), m = INT_MAX; + for (WebPagesPending::const_iterator i = _webPagesPending.cbegin(), e = _webPagesPending.cend(); i != e; ++i) { + if (i.value()) continue; + if (i.key()->pendingTill <= t) { + WebPageItems::const_iterator j = items.constFind(i.key()); + if (j != items.cend() && !j.value().isEmpty()) { + ids.push_back(MTP_int(j.value().begin().key()->id)); + } + } else { + m = qMin(m, i.key()->pendingTill - t); + } + } + if (!ids.isEmpty()) { + mtpRequestId req = MTP::send(MTPmessages_GetMessages(MTP_vector(ids)), rpcDone(&ApiWrap::gotWebPages)); + for (WebPagesPending::iterator i = _webPagesPending.begin(); i != _webPagesPending.cend(); ++i) { + if (i.value()) continue; + if (i.key()->pendingTill <= t) { + i.value() = req; + } + } + } + if (m < INT_MAX) _webPagesTimer.start(m * 1000); +} + void ApiWrap::gotReplyTo(const MTPmessages_Messages &msgs, mtpRequestId req) { switch (msgs.type()) { case mtpc_messages_messages: @@ -176,6 +226,61 @@ void ApiWrap::gotReplyTo(const MTPmessages_Messages &msgs, mtpRequestId req) { } } +void ApiWrap::gotWebPages(const MTPmessages_Messages &msgs, mtpRequestId req) { + const QVector *v = 0; + switch (msgs.type()) { + case mtpc_messages_messages: + App::feedUsers(msgs.c_messages_messages().vusers); + App::feedChats(msgs.c_messages_messages().vchats); + v = &msgs.c_messages_messages().vmessages.c_vector().v; + break; + + case mtpc_messages_messagesSlice: + App::feedUsers(msgs.c_messages_messagesSlice().vusers); + App::feedChats(msgs.c_messages_messagesSlice().vchats); + v = &msgs.c_messages_messagesSlice().vmessages.c_vector().v; + break; + } + + QMap msgsIds; // copied from feedMsgs + for (int32 i = 0, l = v->size(); i < l; ++i) { + const MTPMessage &msg(v->at(i)); + switch (msg.type()) { + case mtpc_message: msgsIds.insert(msg.c_message().vid.v, i); break; + case mtpc_messageEmpty: msgsIds.insert(msg.c_messageEmpty().vid.v, i); break; + case mtpc_messageService: msgsIds.insert(msg.c_messageService().vid.v, i); break; + } + } + + MainWidget *m = App::main(); + for (QMap::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) { + HistoryItem *item = App::histories().addToBack(v->at(*i), -1); + if (item) { + item->initDimensions(); + if (m) m->itemResized(item); + } + } + + const WebPageItems &items(App::webPageItems()); + for (WebPagesPending::iterator i = _webPagesPending.begin(); i != _webPagesPending.cend(); ++i) { + if (i.value() == req) { + if (i.key()->pendingTill > 0) { + i.key()->pendingTill = -1; + WebPageItems::const_iterator j = items.constFind(i.key()); + if (j != items.cend()) { + for (HistoryItemsMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) { + k.key()->initDimensions(); + if (m) m->itemResized(k.key()); + } + } + } + i = _webPagesPending.erase(i); + } else { + ++i; + } + } +} + ApiWrap::~ApiWrap() { App::deinitMedia(false); } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index b8e2e69dab..6a8b80cd72 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -32,6 +32,10 @@ public: void requestFullPeer(PeerData *peer); + void requestWebPageDelayed(WebPageData *page); + void clearWebPageRequest(WebPageData *page); + void clearWebPageRequests(); + ~ApiWrap(); signals: @@ -41,6 +45,7 @@ signals: public slots: void resolveReplyTo(); + void resolveWebPages(); private: @@ -61,4 +66,9 @@ private: typedef QMap FullRequests; FullRequests _fullRequests; + void gotWebPages(const MTPmessages_Messages &result, mtpRequestId req); + typedef QMap WebPagesPending; + WebPagesPending _webPagesPending; + SingleTimer _webPagesTimer; + }; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index e9524f5978..7cf0d78bf5 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -49,9 +49,13 @@ namespace { typedef QHash DocumentsData; DocumentsData documentsData; + typedef QHash WebPagesData; + WebPagesData webPagesData; + VideoItems videoItems; AudioItems audioItems; DocumentItems documentItems; + WebPageItems webPageItems; typedef QMap > RepliesTo; RepliesTo repliesTo; @@ -321,7 +325,7 @@ namespace App { data->inputUser = MTP_inputUserContact(d.vid); data->setName(lang(lng_deleted), QString(), QString(), QString()); data->setPhoto(MTP_userProfilePhotoEmpty()); - data->access = 0; + data->access = UserNoAccess; wasContact = (data->contact > 0); status = &emptyStatus; data->contact = -1; @@ -333,9 +337,10 @@ namespace App { data = App::user(peer); data->input = MTP_inputPeerContact(d.vid); data->inputUser = MTP_inputUserContact(d.vid); - data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), QString(), textOneLine(qs(d.vusername))); + data->setName(lang(lng_deleted), QString(), QString(), QString()); +// data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), QString(), textOneLine(qs(d.vusername))); data->setPhoto(MTP_userProfilePhotoEmpty()); - data->access = 0; + data->access = UserNoAccess; wasContact = (data->contact > 0); status = &emptyStatus; data->contact = -1; @@ -376,7 +381,7 @@ namespace App { } break; case mtpc_userRequest: { const MTPDuserRequest &d(user.c_userRequest()); - + PeerId peer(peerFromUser(d.vid.v)); data = App::user(peer); data->input = MTP_inputPeerForeign(d.vid, d.vaccess_hash); @@ -601,7 +606,7 @@ namespace App { const QVector &v(msgs.c_vector().v); QMap msgsIds; for (int32 i = 0, l = v.size(); i < l; ++i) { - const MTPMessage &msg(v[i]); + const MTPMessage &msg(v.at(i)); switch (msg.type()) { case mtpc_message: msgsIds.insert(msg.c_message().vid.v, i); break; case mtpc_messageEmpty: msgsIds.insert(msg.c_messageEmpty().vid.v, i); break; @@ -609,7 +614,7 @@ namespace App { } } for (QMap::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) { - histories().addToBack(v[*i], msgsState); + histories().addToBack(v.at(*i), msgsState); } } @@ -726,7 +731,7 @@ namespace App { if (user->inputUser.type() != mtpc_inputUserSelf) user->inputUser = MTP_inputUserContact(userId); } } else { - if (user->access) { + if (user->access && user->access != UserNoAccess) { if (user->input.type() != mtpc_inputPeerSelf) user->input = MTP_inputPeerForeign(userId, MTP_long(user->access)); if (user->inputUser.type() != mtpc_inputUserSelf) user->inputUser = MTP_inputUserForeign(userId, MTP_long(user->access)); } @@ -900,6 +905,23 @@ namespace App { return App::document(document.vid.v, convert, document.vaccess_hash.v, document.vdate.v, document.vattributes.c_vector().v, qs(document.vmime_type), App::image(document.vthumb), document.vdc_id.v, document.vsize.v); } + WebPageData *feedWebPage(const MTPDwebPage &webpage, WebPageData *convert) { + return App::webPage(webpage.vid.v, convert, webpage.has_type() ? qs(webpage.vtype) : qsl("article"), qs(webpage.vurl), qs(webpage.vdisplay_url), webpage.has_site_name() ? qs(webpage.vsite_name) : QString(), webpage.has_title() ? qs(webpage.vtitle) : QString(), webpage.has_description() ? qs(webpage.vdescription) : QString(), webpage.has_photo() ? App::feedPhoto(webpage.vphoto) : 0, webpage.has_duration() ? webpage.vduration.v : 0, webpage.has_author() ? qs(webpage.vauthor) : QString()); + } + + WebPageData *feedWebPage(const MTPDwebPagePending &webpage, WebPageData *convert) { + return App::webPage(webpage.vid.v, convert, QString(), QString(), QString(), QString(), QString(), QString(), 0, 0, QString(), webpage.vdate.v); + } + + WebPageData *feedWebPage(const MTPWebPage &webpage) { + switch (webpage.type()) { + case mtpc_webPage: return App::feedWebPage(webpage.c_webPage()); + case mtpc_webPageEmpty: return App::webPage(webpage.c_webPageEmpty().vid.v); + case mtpc_webPagePending: return App::feedWebPage(webpage.c_webPagePending()); + } + return 0; + } + UserData *userLoaded(const PeerId &user) { PeerData *peer = peerLoaded(user); return (peer && peer->loaded) ? peer->asUser() : 0; @@ -1180,6 +1202,80 @@ namespace App { return result; } + WebPageData *webPage(const WebPageId &webPage, WebPageData *convert, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const QString &description, PhotoData *photo, int32 duration, const QString &author, int32 pendingTill) { + if (convert) { + if (convert->id != webPage) { + WebPagesData::iterator i = webPagesData.find(convert->id); + if (i != webPagesData.cend() && i.value() == convert) { + webPagesData.erase(i); + } + convert->id = webPage; + } + if ((convert->url.isEmpty() && !url.isEmpty()) || (convert->pendingTill && convert->pendingTill != pendingTill)) { + convert->type = toWebPageType(type); + convert->url = url; + convert->displayUrl = displayUrl; + convert->siteName = siteName; + convert->title = title; + convert->description = description; + convert->photo = photo; + convert->duration = duration; + convert->author = author; + if (convert->pendingTill > 0 && pendingTill <= 0 && api()) api()->clearWebPageRequest(convert); + convert->pendingTill = pendingTill; + + MainWidget *m = App::main(); + WebPageItems::const_iterator j = ::webPageItems.constFind(convert); + if (j != ::webPageItems.cend()) { + for (HistoryItemsMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) { + k.key()->initDimensions(); + if (m) m->itemResized(k.key()); + } + } + } + } + WebPagesData::const_iterator i = webPagesData.constFind(webPage); + WebPageData *result; + if (i == webPagesData.cend()) { + if (convert) { + result = convert; + } else { + result = new WebPageData(webPage, toWebPageType(type), url, displayUrl, siteName, title, description, photo, duration, author, pendingTill); + if (pendingTill > 0 && api()) { + api()->requestWebPageDelayed(result); + } + } + webPagesData.insert(webPage, result); + } else { + result = i.value(); + if (result != convert) { + if ((result->url.isEmpty() && !url.isEmpty()) || (result->pendingTill && result->pendingTill != pendingTill)) { + result->type = toWebPageType(type); + result->url = url; + result->displayUrl = displayUrl; + result->siteName = siteName; + result->title = title; + result->description = description; + result->photo = photo; + result->duration = duration; + result->author = author; + if (result->pendingTill > 0 && pendingTill <= 0 && api()) api()->clearWebPageRequest(result); + result->pendingTill = pendingTill; + + MainWidget *m = App::main(); + WebPageItems::const_iterator j = ::webPageItems.constFind(result); + if (j != ::webPageItems.cend()) { + for (HistoryItemsMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) { + k.key()->initDimensions(); + if (m) m->itemResized(k.key()); + } + } + } + } + } + return result; + } + ImageLinkData *imageLink(const QString &imageLink, ImageLinkType type, const QString &url) { ImageLinksData::const_iterator i = imageLinksData.constFind(imageLink); ImageLinkData *result; @@ -1384,6 +1480,11 @@ namespace App { delete *i; } documentsData.clear(); + for (WebPagesData::const_iterator i = webPagesData.cbegin(), e = webPagesData.cend(); i != e; ++i) { + delete *i; + } + webPagesData.clear(); + if (api()) api()->clearWebPageRequests(); cSetRecentStickers(RecentStickerPack()); cSetStickersHash(QByteArray()); cSetStickers(AllStickers()); @@ -1391,6 +1492,7 @@ namespace App { ::videoItems.clear(); ::audioItems.clear(); ::documentItems.clear(); + ::webPageItems.clear(); ::repliesTo.clear(); lastPhotos.clear(); lastPhotosMap.clear(); @@ -1683,6 +1785,18 @@ namespace App { return ::documentItems; } + void regWebPageItem(WebPageData *data, HistoryItem *item) { + ::webPageItems[data][item] = true; + } + + void unregWebPageItem(WebPageData *data, HistoryItem *item) { + ::webPageItems[data].remove(item); + } + + const WebPageItems &webPageItems() { + return ::webPageItems; + } + void setProxySettings(QNetworkAccessManager &manager) { if (cConnectionType() == dbictHttpProxy) { const ConnectionProxy &p(cConnectionProxy()); diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index cb4d1f1b42..c0187c256c 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -34,6 +34,7 @@ typedef QMap HistoryItemsMap; typedef QHash VideoItems; typedef QHash AudioItems; typedef QHash DocumentItems; +typedef QHash WebPageItems; namespace App { Application *app(); @@ -96,6 +97,9 @@ namespace App { DocumentData *feedDocument(const MTPdocument &document, const QPixmap &thumb); DocumentData *feedDocument(const MTPdocument &document, DocumentData *convert = 0); DocumentData *feedDocument(const MTPDdocument &document, DocumentData *convert = 0); + WebPageData *feedWebPage(const MTPDwebPage &webpage, WebPageData *convert = 0); + WebPageData *feedWebPage(const MTPDwebPagePending &webpage, WebPageData *convert = 0); + WebPageData *feedWebPage(const MTPWebPage &webpage); UserData *userLoaded(const PeerId &user); ChatData *chatLoaded(const PeerId &chat); @@ -115,6 +119,7 @@ namespace App { VideoData *video(const VideoId &video, VideoData *convert = 0, const uint64 &access = 0, int32 user = 0, int32 date = 0, int32 duration = 0, int32 w = 0, int32 h = 0, const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0); AudioData *audio(const AudioId &audio, AudioData *convert = 0, const uint64 &access = 0, int32 user = 0, int32 date = 0, const QString &mime = QString(), int32 duration = 0, int32 dc = 0, int32 size = 0); DocumentData *document(const DocumentId &document, DocumentData *convert = 0, const uint64 &access = 0, int32 date = 0, const QVector &attributes = QVector(), const QString &mime = QString(), const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0); + WebPageData *webPage(const WebPageId &webPage, WebPageData *convert = 0, const QString &type = QString(), const QString &url = QString(), const QString &displayUrl = QString(), const QString &siteName = QString(), const QString &title = QString(), const QString &description = QString(), PhotoData *photo = 0, int32 duration = 0, const QString &author = QString(), int32 pendingTill = 0); ImageLinkData *imageLink(const QString &imageLink, ImageLinkType type = InvalidImageLink, const QString &url = QString()); void forgetMedia(); @@ -181,6 +186,10 @@ namespace App { void unregDocumentItem(DocumentData *data, HistoryItem *item); const DocumentItems &documentItems(); + void regWebPageItem(WebPageData *data, HistoryItem *item); + void unregWebPageItem(WebPageData *data, HistoryItem *item); + const WebPageItems &webPageItems(); + void setProxySettings(QNetworkAccessManager &manager); void setProxySettings(QTcpSocket &socket); diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index fec3af1d6a..398a4241a7 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -317,8 +317,10 @@ void Application::chatPhotoDone(PeerId peer, const MTPUpdates &updates) { emit peerPhotoDone(peer); } -bool Application::peerPhotoFail(PeerId peer, const RPCError &e) { - LOG(("Application Error: update photo failed %1: %2").arg(e.type()).arg(e.description())); +bool Application::peerPhotoFail(PeerId peer, const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + + LOG(("Application Error: update photo failed %1: %2").arg(error.type()).arg(error.description())); cancelPhotoUpdate(peer); emit peerPhotoFail(peer); return true; @@ -652,8 +654,8 @@ void Application::checkMapVersion() { psRegisterCustomScheme(); if (Local::oldMapVersion()) { QString versionFeatures; - if (DevChannel && Local::oldMapVersion() < 7026) { - versionFeatures = QString::fromUtf8("\xe2\x80\x94 Langs updated, some bugs fixed").replace('@', qsl("@") + QChar(0x200D)); + if (DevChannel && Local::oldMapVersion() < 8001) { + versionFeatures = QString::fromUtf8("\xe2\x80\x94 View all your sessions and terminate any of them\n\xe2\x80\x94 Two-step verification by additional cloud password\n\xe2\x80\x94 Twitter, YouTube, Instagram links preview\n\xe2\x80\x94 Text is pasted from clipboard when clipboard has both text and image and image sending was cancelled").replace('@', qsl("@") + QChar(0x200D)); } else if (!DevChannel && Local::oldMapVersion() < 8000) { versionFeatures = lang(lng_new_version7026).trimmed(); } diff --git a/Telegram/SourceFiles/art/sprite.png b/Telegram/SourceFiles/art/sprite.png index c5b674a5d5..a2ffacaf05 100644 Binary files a/Telegram/SourceFiles/art/sprite.png and b/Telegram/SourceFiles/art/sprite.png differ diff --git a/Telegram/SourceFiles/art/sprite_200x.png b/Telegram/SourceFiles/art/sprite_200x.png index b273cb73c7..6e3f8cc8e3 100644 Binary files a/Telegram/SourceFiles/art/sprite_200x.png and b/Telegram/SourceFiles/art/sprite_200x.png differ diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp index a616475d89..6e0d57f53b 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.cpp +++ b/Telegram/SourceFiles/boxes/abstractbox.cpp @@ -62,7 +62,7 @@ bool AbstractBox::paint(QPainter &p) { return result; } -void AbstractBox::paintTitle(QPainter &p, const QString &title, bool withShadow) { +void AbstractBox::paintTitle(Painter &p, const QString &title, bool withShadow) { if (withShadow) { // paint shadow p.fillRect(0, st::boxTitleHeight, width(), st::scrollDef.topsh, st::scrollDef.shColor->b); @@ -71,7 +71,7 @@ void AbstractBox::paintTitle(QPainter &p, const QString &title, bool withShadow) // paint box title p.setFont(st::boxTitleFont->f); p.setPen(st::black->p); - p.drawText(st::boxTitlePos.x(), st::boxTitlePos.y() + st::boxTitleFont->ascent, title); + p.drawTextLeft(st::boxTitlePos.x(), st::boxTitlePos.y(), width() - 2 * st::boxTitlePos.x(), title); } void AbstractBox::paintGrayTitle(QPainter &p, const QString &title) { diff --git a/Telegram/SourceFiles/boxes/abstractbox.h b/Telegram/SourceFiles/boxes/abstractbox.h index 46be690b6b..b50f2a1338 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.h +++ b/Telegram/SourceFiles/boxes/abstractbox.h @@ -39,7 +39,7 @@ protected: void prepare(); bool paint(QPainter &p); - void paintTitle(QPainter &p, const QString &title, bool withShadow); + void paintTitle(Painter &p, const QString &title, bool withShadow); void paintGrayTitle(QPainter &p, const QString &title); void setMaxHeight(int32 maxHeight); void resizeMaxHeight(int32 newWidth, int32 maxHeight); diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 8b3e84849b..7f65d428b8 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -155,7 +155,7 @@ void AddContactBox::keyPressEvent(QKeyEvent *e) { } void AddContactBox::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); if (paint(p)) return; if (_retryButton.isHidden()) { @@ -226,7 +226,8 @@ void AddContactBox::onSaveSelfDone(const MTPUser &user) { } bool AddContactBox::onSaveSelfFail(const RPCError &error) { - _addRequest = 0; + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + QString err(error.type()); QString firstName = textOneLine(_firstInput.text()), lastName = textOneLine(_lastInput.text()); if (err == "NAME_NOT_MODIFIED") { @@ -247,6 +248,8 @@ bool AddContactBox::onSaveSelfFail(const RPCError &error) { } bool AddContactBox::onSaveFail(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + _addRequest = 0; QString err(error.type()); QString firstName = _firstInput.text().trimmed(), lastName = _lastInput.text().trimmed(); diff --git a/Telegram/SourceFiles/boxes/autolockbox.cpp b/Telegram/SourceFiles/boxes/autolockbox.cpp index 6aa3785df4..ec6ad7c5c4 100644 --- a/Telegram/SourceFiles/boxes/autolockbox.cpp +++ b/Telegram/SourceFiles/boxes/autolockbox.cpp @@ -65,7 +65,7 @@ void AutoLockBox::showAll() { } void AutoLockBox::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); if (paint(p)) return; paintTitle(p, lang(lng_passcode_autolock), true); diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index 36e06a01d0..8231651eab 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -29,9 +29,9 @@ TextParseOptions _confirmBoxTextOptions = { Qt::LayoutDirectionAuto, // dir }; -ConfirmBox::ConfirmBox(const QString &text, const QString &doneText, const QString &cancelText) : _infoMsg(false), -_confirm(this, doneText.isEmpty() ? lang(lng_continue) : doneText, st::btnSelectDone), -_cancel(this, cancelText.isEmpty() ? lang(lng_cancel) : cancelText, st::btnSelectCancel), +ConfirmBox::ConfirmBox(const QString &text, const QString &doneText, const QString &cancelText, const style::flatButton &doneStyle, const style::flatButton &cancelStyle) : _infoMsg(false), +_confirm(this, doneText.isEmpty() ? lang(lng_continue) : doneText, doneStyle), +_cancel(this, cancelText.isEmpty() ? lang(lng_cancel) : cancelText, cancelStyle), _close(this, QString(), st::btnInfoClose), _text(100) { init(text); diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index 96a2bf288f..2cbf4de9f6 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -24,7 +24,7 @@ class ConfirmBox : public AbstractBox, public RPCSender { public: - ConfirmBox(const QString &text, const QString &doneText = QString(), const QString &cancelText = QString()); + ConfirmBox(const QString &text, const QString &doneText = QString(), const QString &cancelText = QString(), const style::flatButton &doneStyle = st::btnSelectDone, const style::flatButton &cancelStyle = st::btnSelectCancel); ConfirmBox(const QString &text, bool noDone, const QString &cancelText = QString()); void keyPressEvent(QKeyEvent *e); void paintEvent(QPaintEvent *e); diff --git a/Telegram/SourceFiles/boxes/connectionbox.cpp b/Telegram/SourceFiles/boxes/connectionbox.cpp index ca116de2e6..76a9c130b9 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.cpp +++ b/Telegram/SourceFiles/boxes/connectionbox.cpp @@ -94,7 +94,7 @@ void ConnectionBox::showDone() { } void ConnectionBox::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); if (paint(p)) return; paintTitle(p, lang(lng_connection_header), true); diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index b789648ad8..db9a9b6741 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -942,6 +942,8 @@ void ContactsBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId r } bool ContactsBox::peopleFailed(const RPCError &error, mtpRequestId req) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + if (_peopleRequest == req) { _peopleRequest = 0; _peopleFull = true; @@ -1000,7 +1002,7 @@ void ContactsBox::keyPressEvent(QKeyEvent *e) { } void ContactsBox::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); if (paint(p)) return; if (_inner.chat() || _inner.creatingChat()) { @@ -1104,7 +1106,7 @@ void CreateGroupBox::keyPressEvent(QKeyEvent *e) { } void CreateGroupBox::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); if (paint(p)) return; paintTitle(p, lang(lng_create_group_title), true); @@ -1161,12 +1163,14 @@ void CreateGroupBox::created(const MTPUpdates &updates) { } } -bool CreateGroupBox::failed(const RPCError &e) { +bool CreateGroupBox::failed(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + _createRequestId = 0; - if (e.type() == "NO_CHAT_TITLE") { + if (error.type() == "NO_CHAT_TITLE") { _name.setFocus(); return true; - } else if (e.type() == "USERS_TOO_FEW") { + } else if (error.type() == "USERS_TOO_FEW") { emit closed(); return true; } diff --git a/Telegram/SourceFiles/boxes/downloadpathbox.cpp b/Telegram/SourceFiles/boxes/downloadpathbox.cpp index 9bfd73734f..eaff7eedd9 100644 --- a/Telegram/SourceFiles/boxes/downloadpathbox.cpp +++ b/Telegram/SourceFiles/boxes/downloadpathbox.cpp @@ -78,7 +78,7 @@ void DownloadPathBox::showAll() { } void DownloadPathBox::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); if (paint(p)) return; paintTitle(p, lang(lng_download_path_header), true); diff --git a/Telegram/SourceFiles/boxes/languagebox.cpp b/Telegram/SourceFiles/boxes/languagebox.cpp index 44be0e8954..45e2b5fae7 100644 --- a/Telegram/SourceFiles/boxes/languagebox.cpp +++ b/Telegram/SourceFiles/boxes/languagebox.cpp @@ -94,7 +94,7 @@ void LanguageBox::mousePressEvent(QMouseEvent *e) { } void LanguageBox::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); if (paint(p)) return; paintTitle(p, lang(lng_languages), true); diff --git a/Telegram/SourceFiles/boxes/passcodebox.cpp b/Telegram/SourceFiles/boxes/passcodebox.cpp index f0d019f7e9..9868918237 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.cpp +++ b/Telegram/SourceFiles/boxes/passcodebox.cpp @@ -24,7 +24,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "localstorage.h" -PasscodeBox::PasscodeBox(bool turningOff) : _turningOff(turningOff), _cloudPwd(false), +PasscodeBox::PasscodeBox(bool turningOff) : _replacedBy(0), _turningOff(turningOff), _cloudPwd(false), _setRequest(0), _hasRecovery(false), _aboutHeight(0), _about(st::boxWidth - st::addContactPadding.left() - st::addContactPadding.right()), _saveButton(this, lang(_turningOff ? lng_passcode_remove_button : lng_settings_save), st::btnSelectDone), @@ -39,7 +39,7 @@ _recover(this, lang(lng_signin_recover)) { prepare(); } -PasscodeBox::PasscodeBox(const QByteArray &newSalt, const QByteArray &curSalt, bool hasRecovery, const QString &hint, bool turningOff) : _turningOff(turningOff), _cloudPwd(true), +PasscodeBox::PasscodeBox(const QByteArray &newSalt, const QByteArray &curSalt, bool hasRecovery, const QString &hint, bool turningOff) : _replacedBy(0), _turningOff(turningOff), _cloudPwd(true), _setRequest(0), _newSalt(newSalt), _curSalt(curSalt), _hasRecovery(hasRecovery), _hint(hint), _aboutHeight(0), _about(st::boxWidth - st::addContactPadding.left() - st::addContactPadding.right()), _saveButton(this, lang(_turningOff ? lng_passcode_remove_button : lng_settings_save), st::btnSelectDone), @@ -81,13 +81,12 @@ void PasscodeBox::init() { connect(&_saveButton, SIGNAL(clicked()), this, SLOT(onSave())); connect(&_cancelButton, SIGNAL(clicked()), this, SLOT(onClose())); - _badOldTimer.setSingleShot(true); - connect(&_badOldTimer, SIGNAL(timeout()), this, SLOT(onBadOldPasscode())); - connect(&_oldPasscode, SIGNAL(changed()), this, SLOT(onOldChanged())); connect(&_newPasscode, SIGNAL(changed()), this, SLOT(onNewChanged())); connect(&_reenterPasscode, SIGNAL(changed()), this, SLOT(onNewChanged())); connect(&_recoverEmail, SIGNAL(changed()), this, SLOT(onEmailChanged())); + + connect(&_recover, SIGNAL(clicked()), this, SLOT(onRecoverByEmail())); } void PasscodeBox::hideAll() { @@ -182,7 +181,7 @@ void PasscodeBox::keyPressEvent(QKeyEvent *e) { } void PasscodeBox::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); if (paint(p)) return; paintTitle(p, _boxTitle, true); @@ -252,11 +251,12 @@ void PasscodeBox::showDone() { void PasscodeBox::setPasswordDone(const MTPBool &result) { _setRequest = 0; emit reloadPassword(); - ConfirmBox *box = new ConfirmBox(lang(_reenterPasscode.isHidden() ? lng_cloud_password_removed : lng_cloud_password_was_set), true, lang(lng_about_done)); + ConfirmBox *box = new ConfirmBox(lang(_reenterPasscode.isHidden() ? lng_cloud_password_removed : (_oldPasscode.isHidden() ? lng_cloud_password_was_set : lng_cloud_password_updated)), true, lang(lng_about_done)); App::wnd()->showLayer(box, true); } bool PasscodeBox::setPasswordFail(const RPCError &error) { + if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); _setRequest = 0; QString err = error.type(); if (err == "PASSWORD_HASH_INVALID") { @@ -283,6 +283,13 @@ bool PasscodeBox::setPasswordFail(const RPCError &error) { ConfirmBox *box = new ConfirmBox(lang(lng_cloud_password_almost), true, lang(lng_about_done)); App::wnd()->showLayer(box, true); emit reloadPassword(); + } else if (error.type().startsWith(qsl("FLOOD_WAIT_"))) { + if (_oldPasscode.isHidden()) return false; + + _oldPasscode.selectAll(); + _oldPasscode.setFocus(); + _oldPasscode.notaBene(); + _oldError = lang(lng_flood_error); } return true; } @@ -293,22 +300,28 @@ void PasscodeBox::onSave(bool force) { QString old = _oldPasscode.text(), pwd = _newPasscode.text(), conf = _reenterPasscode.text(); bool has = _cloudPwd ? (!_curSalt.isEmpty()) : cHasPasscode(); if (!_cloudPwd && (_turningOff || has)) { + if (!passcodeCanTry()) { + _oldError = lang(lng_flood_error); + _oldPasscode.setFocus(); + _oldPasscode.notaBene(); + update(); + return; + } + if (Local::checkPasscode(old.toUtf8())) { + cSetPasscodeBadTries(0); if (_turningOff) pwd = conf = QString(); } else { - _oldPasscode.setDisabled(true); - _newPasscode.setDisabled(true); - _reenterPasscode.setDisabled(true); - _saveButton.setDisabled(true); - _oldError = QString(); - update(); - _badOldTimer.start(WrongPasscodeTimeout); + cSetPasscodeBadTries(cPasscodeBadTries() + 1); + cSetPasscodeLastTry(getms(true)); + onBadOldPasscode(); return; } } if (!_turningOff && pwd.isEmpty()) { _newPasscode.setFocus(); _newPasscode.notaBene(); + if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); return; } if (pwd != conf) { @@ -318,18 +331,28 @@ void PasscodeBox::onSave(bool force) { _newError = lang(_cloudPwd ? lng_cloud_password_differ : lng_passcode_differ); update(); } + if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); } else if (!_turningOff && has && old == pwd) { _newPasscode.setFocus(); _newPasscode.notaBene(); - _newError = lang(_cloudPwd ? lng_cloud_password_differ : lng_passcode_is_same); + _newError = lang(_cloudPwd ? lng_cloud_password_is_same : lng_passcode_is_same); update(); + if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); } else if (_cloudPwd) { QString hint = _passwordHint.text(), email = _recoverEmail.text().trimmed(); + if (_cloudPwd && pwd == hint && !_passwordHint.isHidden() && !_newPasscode.isHidden()) { + _newPasscode.setFocus(); + _newPasscode.notaBene(); + _newError = lang(lng_cloud_password_bad); + update(); + if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); + return; + } if (!_recoverEmail.isHidden() && email.isEmpty() && !force) { - ConfirmBox *box = new ConfirmBox(lang(lng_cloud_password_about_recover)); - connect(box, SIGNAL(confirmed()), this, SLOT(onForceNoMail())); - connect(box, SIGNAL(confirmed()), box, SLOT(onClose())); - App::wnd()->replaceLayer(box); + _replacedBy = new ConfirmBox(lang(lng_cloud_password_about_recover)); + connect(_replacedBy, SIGNAL(confirmed()), this, SLOT(onForceNoMail())); + connect(_replacedBy, SIGNAL(destroyed(QObject*)), this, SLOT(onBoxDestroyed(QObject*))); + App::wnd()->replaceLayer(_replacedBy); } else { QByteArray newPasswordData = pwd.isEmpty() ? QByteArray() : (_newSalt + pwd.toUtf8() + _newSalt); QByteArray newPasswordHash = pwd.isEmpty() ? QByteArray() : QByteArray(32, Qt::Uninitialized); @@ -352,6 +375,7 @@ void PasscodeBox::onSave(bool force) { _setRequest = MTP::send(MTPaccount_UpdatePasswordSettings(MTP_string(oldPasswordHash), settings), rpcDone(&PasscodeBox::setPasswordDone), rpcFail(&PasscodeBox::setPasswordFail)); } } else { + cSetPasscodeBadTries(0); Local::setPasscode(pwd.toUtf8()); App::wnd()->checkAutoLock(); App::wnd()->getTitle()->showUpdateBtn(); @@ -360,10 +384,6 @@ void PasscodeBox::onSave(bool force) { } void PasscodeBox::onBadOldPasscode() { - _oldPasscode.setDisabled(false); - _newPasscode.setDisabled(false); - _reenterPasscode.setDisabled(false); - _saveButton.setDisabled(false); _oldPasscode.selectAll(); _oldPasscode.setFocus(); _oldPasscode.notaBene(); @@ -396,6 +416,12 @@ void PasscodeBox::onForceNoMail() { onSave(true); } +void PasscodeBox::onBoxDestroyed(QObject *obj) { + if (obj == _replacedBy) { + _replacedBy = 0; + } +} + void PasscodeBox::onRecoverByEmail() { if (_pattern.isEmpty()) { _pattern = "-"; @@ -412,10 +438,11 @@ void PasscodeBox::onRecoverExpired() { void PasscodeBox::recover() { if (_pattern == "-") return; - RecoverBox *box = new RecoverBox(_pattern); - connect(box, SIGNAL(reloadPassword()), this, SIGNAL(reloadPassword())); - connect(box, SIGNAL(recoveryExpired()), this, SLOT(onRecoverExpired())); - App::wnd()->replaceLayer(box); + _replacedBy = new RecoverBox(_pattern); + connect(_replacedBy, SIGNAL(reloadPassword()), this, SIGNAL(reloadPassword())); + connect(_replacedBy, SIGNAL(recoveryExpired()), this, SLOT(onRecoverExpired())); + connect(_replacedBy, SIGNAL(destroyed(QObject*)), this, SLOT(onBoxDestroyed(QObject*))); + App::wnd()->replaceLayer(_replacedBy); } void PasscodeBox::recoverStarted(const MTPauth_PasswordRecovery &result) { @@ -424,6 +451,8 @@ void PasscodeBox::recoverStarted(const MTPauth_PasswordRecovery &result) { } bool PasscodeBox::recoverStartFail(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + _pattern = QString(); onClose(); return true; @@ -470,7 +499,7 @@ void RecoverBox::keyPressEvent(QKeyEvent *e) { } void RecoverBox::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); if (paint(p)) return; paintTitle(p, lang(lng_signin_recover), true); @@ -478,8 +507,9 @@ void RecoverBox::paintEvent(QPaintEvent *e) { // paint shadow p.fillRect(0, height() - st::btnSelectCancel.height - st::scrollDef.bottomsh, width(), st::scrollDef.bottomsh, st::scrollDef.shColor->b); + p.setFont(st::usernameFont->f); int32 w = width() - st::addContactPadding.left() - st::addContactPadding.right(); - p.drawText(QRect(st::addContactPadding.left(), _recoverCode.y() - st::usernameSkip, w, st::usernameSkip), st::usernameFont->m.elidedText(_pattern, Qt::ElideRight, w), style::al_center); + p.drawText(QRect(st::addContactPadding.left(), _recoverCode.y() - st::usernameSkip - st::addContactPadding.top(), w, st::addContactPadding.top() + st::usernameSkip), st::usernameFont->m.elidedText(_pattern, Qt::ElideRight, w), style::al_center); if (!_error.isEmpty()) { p.setPen(st::setErrColor->p); @@ -548,8 +578,7 @@ bool RecoverBox::codeSubmitFail(const RPCError &error) { update(); _recoverCode.notaBene(); return true; - } - if (QRegularExpression("^FLOOD_WAIT_(\\d+)$").match(err).hasMatch()) { + } else if (error.type().startsWith(qsl("FLOOD_WAIT_"))) { _error = lang(lng_flood_error); update(); _recoverCode.notaBene(); diff --git a/Telegram/SourceFiles/boxes/passcodebox.h b/Telegram/SourceFiles/boxes/passcodebox.h index f797c490f3..efc167c7f6 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.h +++ b/Telegram/SourceFiles/boxes/passcodebox.h @@ -39,6 +39,7 @@ public slots: void onNewChanged(); void onEmailChanged(); void onForceNoMail(); + void onBoxDestroyed(QObject *obj); void onRecoverByEmail(); void onRecoverExpired(); @@ -63,6 +64,7 @@ private: void recover(); QString _pattern; + AbstractBox *_replacedBy; bool _turningOff, _cloudPwd; mtpRequestId _setRequest; @@ -79,7 +81,6 @@ private: FlatInput _oldPasscode, _newPasscode, _reenterPasscode, _passwordHint, _recoverEmail; LinkButton _recover; - QTimer _badOldTimer; QString _oldError, _newError, _emailError; }; diff --git a/Telegram/SourceFiles/boxes/photosendbox.cpp b/Telegram/SourceFiles/boxes/photosendbox.cpp index bf9ce79db4..212be8a058 100644 --- a/Telegram/SourceFiles/boxes/photosendbox.cpp +++ b/Telegram/SourceFiles/boxes/photosendbox.cpp @@ -210,6 +210,7 @@ void PhotoSendBox::onSend(bool ctrlShiftEnter) { if (App::main()) App::main()->confirmSendImageUncompressed(ctrlShiftEnter, _replyTo); } } + emit confirmed(); emit closed(); } diff --git a/Telegram/SourceFiles/boxes/photosendbox.h b/Telegram/SourceFiles/boxes/photosendbox.h index 232e2bebcb..be6d67df84 100644 --- a/Telegram/SourceFiles/boxes/photosendbox.h +++ b/Telegram/SourceFiles/boxes/photosendbox.h @@ -32,6 +32,10 @@ public: void resizeEvent(QResizeEvent *e); ~PhotoSendBox(); +signals: + + void confirmed(); + public slots: void onSend(bool ctrlShiftEnter = false); diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp index 0d10b5b690..b96277b2d9 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.cpp +++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp @@ -106,6 +106,8 @@ void SessionsInner::terminateDone(uint64 hash, const MTPBool &result) { } bool SessionsInner::terminateFail(uint64 hash, const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + TerminateButtons::iterator i = _terminateButtons.find(hash); if (i != _terminateButtons.end()) { i.value()->show(); @@ -257,18 +259,18 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { SessionData data; data.hash = d.vhash.v; - QString appName, systemVer = qs(d.vsystem_version); - if (d.vapi_id.v == 2040 || d.vapi_id.v == 17349) { - appName = (d.vapi_id.v == 2040) ? qsl("Telegram Desktop") : qsl("Telegram Desktop (GitHub)"); + QString appName, systemVer = qs(d.vsystem_version), deviceModel = qs(d.vdevice_model); + if (d.vapi_id.v == 17349) { + appName = qs(d.vapp_name);// (d.vapi_id.v == 2040) ? qsl("Telegram Desktop") : qsl("Telegram Desktop (GitHub)"); if (systemVer == QLatin1String("windows")) { - systemVer = qsl("Windows"); + deviceModel = qsl("Windows"); } else if (systemVer == QLatin1String("os x")) { - systemVer = qsl("Mac OS X"); + deviceModel = qsl("Mac OS X"); } else if (systemVer == QLatin1String("linux")) { - systemVer = qsl("Linux"); + deviceModel = qsl("Linux"); } } else { - appName = qs(d.vapp_name); + appName = qs(d.vapp_name);// +qsl(" for ") + qs(d.vplatform); } data.name = appName; data.nameWidth = st::sessionNameFont->m.width(data.name); @@ -277,7 +279,7 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { CountriesByISO2::const_iterator j = countries.constFind(country); if (j != countries.cend()) country = QString::fromUtf8(j.value()->name); - data.info = country + QLatin1String(" (") + qs(d.vip) + QLatin1String("), ") + systemVer; + data.info = country + QLatin1String(" (") + qs(d.vip) + QLatin1String("), ") + deviceModel; if (!data.hash || (d.vflags.v & 1)) { data.active = QString(); data.activeWidth = 0; @@ -292,7 +294,16 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { } _current = data; } else { - data.active = date(d.vdate_active.v ? d.vdate_active : d.vdate_created).toString(qsl("hh:mm")); + QDateTime now(QDateTime::currentDateTime()), lastTime(date(d.vdate_active.v ? d.vdate_active : d.vdate_created)); + QDate nowDate(now.date()), lastDate(lastTime.date()); + QString dt; + if (lastDate == nowDate) { + data.active = lastTime.toString(cTimeFormat()); + } else if (lastDate.year() == nowDate.year() && lastDate.weekNumber() == nowDate.weekNumber()) { + data.active = langDayOfWeek(lastDate); + } else { + data.active = lastDate.toString(qsl("d.MM.yy")); + } data.activeWidth = st::sessionActiveFont->m.width(data.active); int32 availForName = availOther - st::sessionPadding.right() - data.activeWidth; if (data.nameWidth > availForName) { @@ -381,6 +392,8 @@ void SessionsBox::terminateAllDone(const MTPBool &result) { } bool SessionsBox::terminateAllFail(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + MTP::send(MTPaccount_GetAuthorizations(), rpcDone(&SessionsBox::gotAuthorizations)); if (_shortPollRequest) { MTP::cancel(_shortPollRequest); diff --git a/Telegram/SourceFiles/boxes/usernamebox.cpp b/Telegram/SourceFiles/boxes/usernamebox.cpp index 8712897b0b..60b49eb50d 100644 --- a/Telegram/SourceFiles/boxes/usernamebox.cpp +++ b/Telegram/SourceFiles/boxes/usernamebox.cpp @@ -99,7 +99,7 @@ void UsernameBox::keyPressEvent(QKeyEvent *e) { } void UsernameBox::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); if (paint(p)) return; paintTitle(p, lang(lng_username_title), true); @@ -194,6 +194,8 @@ void UsernameBox::onUpdateDone(const MTPUser &user) { } bool UsernameBox::onUpdateFail(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + _saveRequest = 0; QString err(error.type()), name = getName(); if (err == "USERNAME_NOT_MODIFIED" || _sentUsername == App::self()->username) { @@ -227,6 +229,8 @@ void UsernameBox::onCheckDone(const MTPBool &result) { } bool UsernameBox::onCheckFail(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + _checkRequest = 0; QString err(error.type()); if (err == "USERNAME_INVALID") { diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index b4de74b30a..a9a23140f7 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -121,6 +121,7 @@ enum { HiddenIsOnlineAfterMessage = 30, // user with hidden last seen stays online for such amount of seconds in the interface ServiceUserId = 777000, + WebPageUserId = 701000, CacheBackgroundTimeout = 3000, // cache background scaled image after 3s BackgroundsInRow = 3, diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index ca3df09dc2..6aeb84c0c5 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -464,6 +464,7 @@ void DialogsListWidget::enterEvent(QEvent *e) { void DialogsListWidget::leaveEvent(QEvent *e) { setMouseTracking(false); + selByMouse = false; if (sel || filteredSel >= 0 || hashtagSel >= 0 || searchedSel >= 0 || peopleSel >= 0) { sel = 0; filteredSel = searchedSel = peopleSel = hashtagSel = -1; @@ -745,6 +746,7 @@ int32 DialogsListWidget::addNewContact(int32 uid, bool select) { sel = added; contactSel = true; } + if (contactsNoDialogs.list.count == 1 && !dialogs.list.count) refresh(); return added ? ((dialogs.list.count + added->pos) * st::dlgHeight) : -1; } if (select) { @@ -1091,7 +1093,7 @@ bool DialogsListWidget::choosePeer() { if (msgId) { saveRecentHashtags(filter); } - bool chosen = (!App::main()->selectingPeer() && (_state == FilteredState || _state == SearchedState) && filteredSel >= 0 && filteredSel < filterResults.size()); + bool chosen = (!App::main()->selectingPeer(true) && (_state == FilteredState || _state == SearchedState) && filteredSel >= 0 && filteredSel < filterResults.size()); App::main()->showPeer(history->peer->id, msgId); if (chosen) { emit searchResultChosen(); @@ -1519,8 +1521,10 @@ void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs) { } } -bool DialogsWidget::dialogsFailed(const RPCError &e) { - LOG(("RPC Error: %1 %2: %3").arg(e.code()).arg(e.type()).arg(e.description())); +bool DialogsWidget::dialogsFailed(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + + LOG(("RPC Error: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description())); dlgPreloading = 0; return true; } @@ -1614,7 +1618,9 @@ void DialogsWidget::contactsReceived(const MTPcontacts_Contacts &contacts) { } } -bool DialogsWidget::contactsFailed() { +bool DialogsWidget::contactsFailed(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + return true; } @@ -1680,6 +1686,8 @@ void DialogsWidget::peopleReceived(const MTPcontacts_Found &result, mtpRequestId } bool DialogsWidget::searchFailed(const RPCError &error, mtpRequestId req) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + if (_searchRequest == req) { _searchRequest = 0; _searchFull = true; @@ -1688,6 +1696,8 @@ bool DialogsWidget::searchFailed(const RPCError &error, mtpRequestId req) { } bool DialogsWidget::peopleFailed(const RPCError &error, mtpRequestId req) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + if (_peopleRequest == req) { _peopleRequest = 0; _peopleFull = true; diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index d98ff14ae3..3082250344 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -232,8 +232,8 @@ private: bool _drawShadow; void unreadCountsReceived(const QVector &dialogs); - bool dialogsFailed(const RPCError &e); - bool contactsFailed(); + bool dialogsFailed(const RPCError &error); + bool contactsFailed(const RPCError &error); bool searchFailed(const RPCError &error, mtpRequestId req); bool peopleFailed(const RPCError &error, mtpRequestId req); diff --git a/Telegram/SourceFiles/fileuploader.cpp b/Telegram/SourceFiles/fileuploader.cpp index 763ab4a229..dbb5d38808 100644 --- a/Telegram/SourceFiles/fileuploader.cpp +++ b/Telegram/SourceFiles/fileuploader.cpp @@ -256,7 +256,9 @@ void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) { sendNext(); } -bool FileUploader::partFailed(const RPCError &err, mtpRequestId requestId) { +bool FileUploader::partFailed(const RPCError &error, mtpRequestId requestId) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + if (requestsSent.constFind(requestId) != requestsSent.cend() || docRequestsSent.constFind(requestId) != docRequestsSent.cend()) { // failed to upload current file currentFailed(); } diff --git a/Telegram/SourceFiles/gui/flattextarea.cpp b/Telegram/SourceFiles/gui/flattextarea.cpp index ae117810a7..cebf39879f 100644 --- a/Telegram/SourceFiles/gui/flattextarea.cpp +++ b/Telegram/SourceFiles/gui/flattextarea.cpp @@ -68,6 +68,10 @@ void FlatTextarea::onTouchTimer() { _touchRightButton = true; } +void FlatTextarea::insertFromMimeData(const QMimeData *source) { + QTextEdit::insertFromMimeData(source); +} + bool FlatTextarea::viewportEvent(QEvent *e) { if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { QTouchEvent *ev = static_cast(e); diff --git a/Telegram/SourceFiles/gui/flattextarea.h b/Telegram/SourceFiles/gui/flattextarea.h index 4f09c69733..f221204b45 100644 --- a/Telegram/SourceFiles/gui/flattextarea.h +++ b/Telegram/SourceFiles/gui/flattextarea.h @@ -29,6 +29,8 @@ public: FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &ph = QString(), const QString &val = QString()); QString val() const; + void insertFromMimeData(const QMimeData *source); + bool viewportEvent(QEvent *e); void touchEvent(QTouchEvent *e); void paintEvent(QPaintEvent *e); diff --git a/Telegram/SourceFiles/gui/scrollarea.cpp b/Telegram/SourceFiles/gui/scrollarea.cpp index 1b9e9f0275..ac0a521d57 100644 --- a/Telegram/SourceFiles/gui/scrollarea.cpp +++ b/Telegram/SourceFiles/gui/scrollarea.cpp @@ -54,7 +54,7 @@ ScrollBar::ScrollBar(ScrollArea *parent, bool vert, const style::flatScroll *st) } void ScrollBar::recountSize() { - setGeometry(_vertical ? QRect(_area->width() - _st->width, 0, _st->width, _area->height()) : QRect(0, _area->height() - _st->width, _area->width(), _st->width)); + setGeometry(_vertical ? QRect(rtl() ? 0 : (_area->width() - _st->width), 0, _st->width, _area->height()) : QRect(0, _area->height() - _st->width, _area->width(), _st->width)); } void ScrollBar::updateBar(bool force) { diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp index 8055f259bc..2ad36c315c 100644 --- a/Telegram/SourceFiles/gui/text.cpp +++ b/Telegram/SourceFiles/gui/text.cpp @@ -854,7 +854,7 @@ void TextLink::onClick(Qt::MouseButton button) const { } else if (QRegularExpression(qsl("^tg://[a-zA-Z0-9]+"), QRegularExpression::CaseInsensitiveOption).match(url).hasMatch()) { App::openLocalUrl(url); } else { - QDesktopServices::openUrl(TextLink::encoded()); + QDesktopServices::openUrl(url); } } } @@ -881,7 +881,7 @@ public: return _blockEnd(t, i, e) - (*i)->from(); } - TextPainter(QPainter *p, const Text *t) : _p(p), _t(t), _elideLast(false), _str(0), _elideSavedBlock(0), _lnkResult(0), _inTextFlag(0), _getSymbol(0), _getSymbolAfter(0), _getSymbolUpon(0) { + TextPainter(QPainter *p, const Text *t) : _p(p), _t(t), _elideLast(false), _elideRemoveFromEnd(0), _str(0), _elideSavedBlock(0), _lnkResult(0), _inTextFlag(0), _getSymbol(0), _getSymbolAfter(0), _getSymbolUpon(0) { } void initNextParagraph(Text::TextBlocks::const_iterator i) { @@ -1010,6 +1010,9 @@ public: last_rBearing = _rb; last_rPadding = b->f_rpadding(); _wLeft = _w - (b->f_width() - last_rBearing); + if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yTo)) { + _wLeft -= _elideRemoveFromEnd; + } _parDirection = static_cast(b)->nextDirection(); if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = langDir(); @@ -1079,6 +1082,9 @@ public: last_rBearing = j->f_rbearing(); last_rPadding = j->rpadding; _wLeft = _w - (j_width - last_rBearing); + if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yTo)) { + _wLeft -= _elideRemoveFromEnd; + } longWordLine = true; f = j + 1; @@ -1102,6 +1108,9 @@ public: last_rBearing = _rb; last_rPadding = b->f_rpadding(); _wLeft = _w - (b->f_width() - last_rBearing); + if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yTo)) { + _wLeft -= _elideRemoveFromEnd; + } longWordLine = true; continue; @@ -1116,12 +1125,13 @@ public: } } - void drawElided(int32 left, int32 top, int32 w, style::align align, int32 lines, int32 yFrom, int32 yTo) { + void drawElided(int32 left, int32 top, int32 w, style::align align, int32 lines, int32 yFrom, int32 yTo, int32 removeFromEnd) { if (lines <= 0) return; if (yTo < 0 || (lines - 1) * _t->_font->height < yTo) { yTo = lines * _t->_font->height; _elideLast = true; + _elideRemoveFromEnd = removeFromEnd; } draw(left, top, w, align, yFrom, yTo); } @@ -1575,7 +1585,7 @@ public: eShapeLine(line); int32 elideWidth = _f->m.width(_Elide); - _wLeft = _w - elideWidth; + _wLeft = _w - elideWidth - _elideRemoveFromEnd; int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1); int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0, i; @@ -2264,6 +2274,7 @@ private: QPainter *_p; const Text *_t; bool _elideLast; + int32 _elideRemoveFromEnd; style::align _align; QPen _originalPen; int32 _yFrom, _yTo; @@ -2326,6 +2337,20 @@ Text::Text(style::font font, const QString &text, const TextParseOptions &option } } +Text::Text(const Text &other) : +_minResizeWidth(other._minResizeWidth), _maxWidth(other._maxWidth), +_minHeight(other._minHeight), +_text(other._text), +_font(other._font), +_blocks(other._blocks.size()), +_links(other._links), +_startDir(other._startDir) +{ + for (int32 i = 0, l = _blocks.size(); i < l; ++i) { + _blocks[i] = other._blocks.at(i)->clone(); + } +} + void Text::setText(style::font font, const QString &text, const TextParseOptions &options) { if (!_textStyle) _initDefault(); _font = font; @@ -2597,10 +2622,10 @@ void Text::draw(QPainter &painter, int32 left, int32 top, int32 w, style::align p.draw(left, top, w, align, yFrom, yTo, selectedFrom, selectedTo); } -void Text::drawElided(QPainter &painter, int32 left, int32 top, int32 w, int32 lines, style::align align, int32 yFrom, int32 yTo) const { +void Text::drawElided(QPainter &painter, int32 left, int32 top, int32 w, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd) const { // painter.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug TextPainter p(&painter, this); - p.drawElided(left, top, w, align, lines, yFrom, yTo); + p.drawElided(left, top, w, align, lines, yFrom, yTo, removeFromEnd); } const TextLinkPtr &Text::link(int32 x, int32 y, int32 width, style::align align) const { diff --git a/Telegram/SourceFiles/gui/text.h b/Telegram/SourceFiles/gui/text.h index 894e53fe23..f2683066e5 100644 --- a/Telegram/SourceFiles/gui/text.h +++ b/Telegram/SourceFiles/gui/text.h @@ -105,6 +105,7 @@ public: return tmp;//_color; } + virtual ITextBlock *clone() const = 0; virtual ~ITextBlock() { } @@ -125,6 +126,10 @@ public: return _nextDir; } + ITextBlock *clone() const { + return new NewlineBlock(*this); + } + private: NewlineBlock(const style::font &font, const QString &str, uint16 from, uint16 length) : ITextBlock(font, str, from, length, 0, st::transparent, 0), _nextDir(Qt::LayoutDirectionAuto) { @@ -160,6 +165,10 @@ public: return _words.isEmpty() ? 0 : _words.back().f_rbearing(); } + ITextBlock *clone() const { + return new TextBlock(*this); + } + private: TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex); @@ -177,6 +186,10 @@ private: class EmojiBlock : public ITextBlock { public: + ITextBlock *clone() const { + return new EmojiBlock(*this); + } + private: EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex, const EmojiData *emoji); @@ -196,6 +209,10 @@ public: return _height; } + ITextBlock *clone() const { + return new SkipBlock(*this); + } + private: SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex); @@ -399,6 +416,7 @@ public: Text(int32 minResizeWidth = QFIXED_MAX); Text(style::font font, const QString &text, const TextParseOptions &options = _defaultOptions, int32 minResizeWidth = QFIXED_MAX, bool richText = false); + Text(const Text &other); int32 countHeight(int32 width) const; void setText(style::font font, const QString &text, const TextParseOptions &options = _defaultOptions); @@ -417,13 +435,16 @@ public: void replaceFont(style::font f); // does not recount anything, use at your own risk! void draw(QPainter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, uint16 selectedFrom = 0, uint16 selectedTo = 0) const; - void drawElided(QPainter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1) const; + void drawElided(QPainter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0) const; const TextLinkPtr &link(int32 x, int32 y, int32 width, style::align align = style::al_left) const; void getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y, int32 width, style::align align = style::al_left) const; void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y, int32 width, style::align align = style::al_left) const; uint32 adjustSelection(uint16 from, uint16 to, TextSelectType selectType) const; + bool isEmpty() const { + return _text.isEmpty(); + } QString original(uint16 selectedFrom = 0, uint16 selectedTo = 0xFFFF, bool expandLinks = true) const; bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index cda652ea9b..d9d157c4be 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -42,60 +42,7 @@ TextParseOptions _textDlgOptions = { Qt::LayoutDirectionAuto, // lang-dependent }; -style::color peerColor(int32 index) { - static const style::color peerColors[8] = { - style::color(st::color1), - style::color(st::color2), - style::color(st::color3), - style::color(st::color4), - style::color(st::color5), - style::color(st::color6), - style::color(st::color7), - style::color(st::color8) - }; - return peerColors[index]; -} - -ImagePtr userDefPhoto(int32 index) { - static const ImagePtr userDefPhotos[8] = { - ImagePtr(qsl(":/ava/art/usercolor1.png"), "PNG"), - ImagePtr(qsl(":/ava/art/usercolor2.png"), "PNG"), - ImagePtr(qsl(":/ava/art/usercolor3.png"), "PNG"), - ImagePtr(qsl(":/ava/art/usercolor4.png"), "PNG"), - ImagePtr(qsl(":/ava/art/usercolor5.png"), "PNG"), - ImagePtr(qsl(":/ava/art/usercolor6.png"), "PNG"), - ImagePtr(qsl(":/ava/art/usercolor7.png"), "PNG"), - ImagePtr(qsl(":/ava/art/usercolor8.png"), "PNG"), - }; - return userDefPhotos[index]; -} - -ImagePtr chatDefPhoto(int32 index) { - static const ImagePtr chatDefPhotos[4] = { - ImagePtr(qsl(":/ava/art/chatcolor1.png"), "PNG"), - ImagePtr(qsl(":/ava/art/chatcolor2.png"), "PNG"), - ImagePtr(qsl(":/ava/art/chatcolor3.png"), "PNG"), - ImagePtr(qsl(":/ava/art/chatcolor4.png"), "PNG"), - }; - return chatDefPhotos[index]; -} - namespace { - int32 peerColorIndex(const PeerId &peer) { - int32 myId(MTP::authedId()), peerId(peer & 0xFFFFFFFFL); - bool chat = (peer & 0x100000000L); - if (chat) { - int ch = 0; - } - QByteArray both(qsl("%1%2").arg(peerId).arg(myId).toUtf8()); - if (both.size() > 15) { - both = both.mid(0, 15); - } - uchar md5[16]; - hashMd5(both.constData(), both.size(), md5); - return (md5[peerId & 0x0F] & (chat ? 0x03 : 0x07)); - } - TextParseOptions _historyTextOptions = { TextParseLinks | TextParseMultiline | TextParseRichText, // flags 0, // maxw @@ -108,10 +55,24 @@ namespace { 0, // maxh Qt::LayoutDirectionAuto, // lang-dependent }; + TextParseOptions _webpageTitleOptions = { + TextParseMultiline | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir + }; + TextParseOptions _webpageDescriptionOptions = { + TextParseMultiline | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir + }; inline void _initTextOptions() { _historySrvOptions.dir = _textNameOptions.dir = _textDlgOptions.dir = langDir(); _textDlgOptions.maxw = st::dlgMaxWidth * 2; + _webpageTitleOptions.maxh = st::webPageTitleFont->height * 2; + _webpageDescriptionOptions.maxh = st::webPageDescriptionFont->height * 3; } class AnimatedGif : public Animated { @@ -204,7 +165,7 @@ namespace { anim::start(this); msg->initDimensions(); - App::main()->itemResized(msg); + App::main()->itemResized(msg, true); } void stop(bool onItemRemoved = false) { @@ -219,7 +180,7 @@ namespace { anim::stop(this); if (row && !onItemRemoved) { row->initDimensions(); - if (App::main()) App::main()->itemResized(row); + if (App::main()) App::main()->itemResized(row, true); } } @@ -273,550 +234,6 @@ void stopGif() { animated.stop(); } -NotifySettings globalNotifyAll, globalNotifyUsers, globalNotifyChats; -NotifySettingsPtr globalNotifyAllPtr = UnknownNotifySettings, globalNotifyUsersPtr = UnknownNotifySettings, globalNotifyChatsPtr = UnknownNotifySettings; - -PeerData::PeerData(const PeerId &id) : id(id) -, loaded(false) -, chat(App::isChat(id)) -, access(0) -, colorIndex(peerColorIndex(id)) -, color(peerColor(colorIndex)) -, photo(chat ? chatDefPhoto(colorIndex) : userDefPhoto(colorIndex)) -, nameVersion(0) -, notify(UnknownNotifySettings) -{ -} - -UserData *PeerData::asUser() { - return chat ? App::user(id & 0xFFFFFFFFL) : static_cast(this); -} - -const UserData *PeerData::asUser() const { - return chat ? App::user(id & 0xFFFFFFFFL) : static_cast(this); -} - -ChatData *PeerData::asChat() { - return chat ? static_cast(this) : App::chat(id | 0x100000000L); -} - -const ChatData *PeerData::asChat() const { - return chat ? static_cast(this) : App::chat(id | 0x100000000L); -} - -void PeerData::updateName(const QString &newName, const QString &newNameOrPhone, const QString &newUsername) { - if (name == newName && nameOrPhone == newNameOrPhone && (chat || asUser()->username == newUsername) && nameVersion > 0) return; - - ++nameVersion; - name = newName; - nameOrPhone = newNameOrPhone; - if (!chat) asUser()->username = newUsername; - Names oldNames = names; - NameFirstChars oldChars = chars; - fillNames(); - App::history(id)->updateNameText(); - if (App::main()) { - emit App::main()->peerNameChanged(this, oldNames, oldChars); - } - nameUpdated(); -} - -void UserData::setPhoto(const MTPUserProfilePhoto &p) { - switch (p.type()) { - case mtpc_userProfilePhoto: { - const MTPDuserProfilePhoto d(p.c_userProfilePhoto()); - photoId = d.vphoto_id.v; - photo = ImagePtr(160, 160, d.vphoto_small, userDefPhoto(colorIndex)); -// App::feedPhoto(App::photoFromUserPhoto(MTP_int(id & 0xFFFFFFFF), MTP_int(unixtime()), p)); - } break; - default: { - photoId = 0; - if (id == ServiceUserId) { - photo = ImagePtr(QPixmap::fromImage(App::wnd()->iconLarge().scaledToWidth(160, Qt::SmoothTransformation), Qt::ColorOnly), "PNG"); - } else { - photo = userDefPhoto(colorIndex); - } - } break; - } - emit App::main()->peerPhotoChanged(this); -} - -void PeerData::fillNames() { - names.clear(); - chars.clear(); - QString toIndex = textAccentFold(name); - if (nameOrPhone != name) { - toIndex += ' ' + textAccentFold(nameOrPhone); - } - if (!chat) { - toIndex += ' ' + textAccentFold(asUser()->username); - } - if (cRussianLetters().match(toIndex).hasMatch()) { - toIndex += ' ' + translitRusEng(toIndex); - } - toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex); - - QStringList namesList = toIndex.toLower().split(cWordSplit(), QString::SkipEmptyParts); - for (QStringList::const_iterator i = namesList.cbegin(), e = namesList.cend(); i != e; ++i) { - names.insert(*i); - chars.insert(i->at(0)); - } -} - - -void UserData::setName(const QString &first, const QString &last, const QString &phoneName, const QString &usern) { - bool updName = !first.isEmpty() || !last.isEmpty(), updUsername = (username != usern); - - if (updName && first.trimmed().isEmpty()) { - firstName = last; - lastName = QString(); - updateName(firstName, phoneName, usern); - } else { - if (updName) { - firstName = first; - lastName = last; - } - updateName(lastName.isEmpty() ? firstName : (firstName + ' ' + lastName), phoneName, usern); - } - if (updUsername) { - if (App::main()) { - App::main()->peerUsernameChanged(this); - } - } -} - -void UserData::setPhone(const QString &newPhone) { - phone = newPhone; - ++nameVersion; -} - -void UserData::nameUpdated() { - nameText.setText(st::msgNameFont, name, _textNameOptions); -} - -void ChatData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { - switch (p.type()) { - case mtpc_chatPhoto: { - const MTPDchatPhoto d(p.c_chatPhoto()); - photo = ImagePtr(160, 160, d.vphoto_small, chatDefPhoto(colorIndex)); - photoFull = ImagePtr(640, 640, d.vphoto_big, chatDefPhoto(colorIndex)); - if (phId) { - photoId = phId; - } - } break; - default: { - photo = chatDefPhoto(colorIndex); - photoFull = ImagePtr(); - photoId = 0; - } break; - } - emit App::main()->peerPhotoChanged(this); -} - -void PhotoLink::onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton) { - App::wnd()->showPhoto(this, App::hoveredLinkItem()); - } -} - -QString saveFileName(const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir = QDir()) { -#ifdef Q_OS_WIN - name = name.replace(QRegularExpression(qsl("[\\\\\\/\\:\\*\\?\\\"\\<\\>\\|]")), qsl("_")); -#elif defined Q_OS_MAC - name = name.replace(QRegularExpression(qsl("[\\:]")), qsl("_")); -#elif defined Q_OS_LINUX - name = name.replace(QRegularExpression(qsl("[\\/]")), qsl("_")); -#endif - if (cAskDownloadPath() || savingAs) { - if (!name.isEmpty() && name.at(0) == QChar::fromLatin1('.')) { - name = filedialogDefaultName(prefix, name); - } else if (dir.path() != qsl(".")) { - QString path = dir.absolutePath(); - if (path != cDialogLastPath()) { - cSetDialogLastPath(path); - Local::writeUserSettings(); - } - } - - return filedialogGetSaveFile(name, title, filter, name) ? name : QString(); - } - - QString path; - if (cDownloadPath().isEmpty()) { - path = psDownloadPath(); - } else if (cDownloadPath() == qsl("tmp")) { - path = cTempDir(); - } else { - path = cDownloadPath(); - } - if (name.isEmpty()) name = qsl(".unknown"); - if (name.at(0) == QChar::fromLatin1('.')) { - if (!QDir().exists(path)) QDir().mkpath(path); - return filedialogDefaultName(prefix, name, path); - } - if (dir.path() != qsl(".")) { - path = dir.absolutePath() + '/'; - } - - QString nameStart, extension; - int32 extPos = name.lastIndexOf('.'); - if (extPos >= 0) { - nameStart = name.mid(0, extPos); - extension = name.mid(extPos); - } else { - nameStart = name; - } - QString nameBase = path + nameStart; - name = nameBase + extension; - for (int i = 0; QFileInfo(name).exists(); ++i) { - name = nameBase + QString(" (%1)").arg(i + 2) + extension; - } - - if (!QDir().exists(path)) QDir().mkpath(path); - return name; -} - -void VideoOpenLink::onClick(Qt::MouseButton button) const { - VideoData *data = video(); - if ((!data->user && !data->date) || button != Qt::LeftButton) return; - - QString already = data->already(true); - if (!already.isEmpty()) { - psOpenFile(already); - return; - } - - if (data->status != FileReady) return; - - QString filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), qsl(".mov"), false); - if (!filename.isEmpty()) { - data->openOnSave = 1; - data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0; - data->save(filename); - } -} - -void VideoSaveLink::doSave(bool forceSavingAs) const { - VideoData *data = video(); - if (!data->user && !data->date) return; - - QString already = data->already(true); - if (!already.isEmpty() && !forceSavingAs) { - QPoint pos(QCursor::pos()); - if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { - psOpenFile(already, true); - } - } else { - QFileInfo alreadyInfo(already); - QDir alreadyDir(already.isEmpty() ? QDir() : alreadyInfo.dir()); - QString name = already.isEmpty() ? QString(".mov") : alreadyInfo.fileName(); - QString filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), name, forceSavingAs, alreadyDir); - if (!filename.isEmpty()) { - if (forceSavingAs) { - data->cancel(); - } else if (!already.isEmpty()) { - data->openOnSave = -1; - data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0; - } - data->save(filename); - } - } -} - -void VideoSaveLink::onClick(Qt::MouseButton button) const { - if (button != Qt::LeftButton) return; - doSave(); -} - -void VideoCancelLink::onClick(Qt::MouseButton button) const { - VideoData *data = video(); - if ((!data->user && !data->date) || button != Qt::LeftButton) return; - - data->cancel(); -} - -VideoData::VideoData(const VideoId &id, const uint64 &access, int32 user, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size) : -id(id), access(access), user(user), date(date), duration(duration), w(w), h(h), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), fileType(0), openOnSave(0), openOnSaveMsgId(0), loader(0) { - location = Local::readFileLocation(mediaKey(mtpc_inputVideoFileLocation, dc, id)); -} - -void VideoData::save(const QString &toFile) { - cancel(true); - loader = new mtpFileLoader(dc, id, access, mtpc_inputVideoFileLocation, toFile, size); - loader->connect(loader, SIGNAL(progress(mtpFileLoader*)), App::main(), SLOT(videoLoadProgress(mtpFileLoader*))); - loader->connect(loader, SIGNAL(failed(mtpFileLoader*,bool)), App::main(), SLOT(videoLoadFailed(mtpFileLoader*,bool))); - loader->start(); -} - -QString VideoData::already(bool check) { - if (!check) return location.name; - if (!location.check()) location = Local::readFileLocation(mediaKey(mtpc_inputVideoFileLocation, dc, id)); - return location.name; -} - -void AudioOpenLink::onClick(Qt::MouseButton button) const { - AudioData *data = audio(); - if ((!data->user && !data->date) || button != Qt::LeftButton) return; - - bool mp3 = (data->mime == QLatin1String("audio/mp3")); - - QString already = data->already(true); - bool play = !mp3 && audioVoice(); - if (!already.isEmpty() || (!data->data.isEmpty() && play)) { - if (play) { - AudioData *playing = 0; - VoiceMessageState playingState = VoiceMessageStopped; - audioVoice()->currentState(&playing, &playingState); - if (playing == data && playingState != VoiceMessageStopped) { - audioVoice()->pauseresume(); - } else { - audioVoice()->play(data); - } - } else { - psOpenFile(already); - } - return; - } - - if (data->status != FileReady) return; - - QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), mp3 ? qsl(".mp3") : qsl(".ogg"), false); - if (!filename.isEmpty()) { - data->openOnSave = 1; - data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0; - data->save(filename); - } -} - -void AudioSaveLink::doSave(bool forceSavingAs) const { - AudioData *data = audio(); - if (!data->user && !data->date) return; - - QString already = data->already(true); - if (!already.isEmpty() && !forceSavingAs) { - QPoint pos(QCursor::pos()); - if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { - psOpenFile(already, true); - } - } else { - QFileInfo alreadyInfo(already); - QDir alreadyDir(already.isEmpty() ? QDir() : alreadyInfo.dir()); - bool mp3 = (data->mime == QLatin1String("audio/mp3")); - QString name = already.isEmpty() ? (mp3 ? qsl(".mp3") : qsl(".ogg")) : alreadyInfo.fileName(); - QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), name, forceSavingAs, alreadyDir); - if (!filename.isEmpty()) { - if (forceSavingAs) { - data->cancel(); - } else if (!already.isEmpty()) { - data->openOnSave = -1; - data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0; - } - data->save(filename); - } - } -} - -void AudioSaveLink::onClick(Qt::MouseButton button) const { - if (button != Qt::LeftButton) return; - doSave(); -} - -void AudioCancelLink::onClick(Qt::MouseButton button) const { - AudioData *data = audio(); - if ((!data->user && !data->date) || button != Qt::LeftButton) return; - - data->cancel(); -} - -AudioData::AudioData(const AudioId &id, const uint64 &access, int32 user, int32 date, const QString &mime, int32 duration, int32 dc, int32 size) : -id(id), access(access), user(user), date(date), mime(mime), duration(duration), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), openOnSaveMsgId(0), loader(0) { - location = Local::readFileLocation(mediaKey(mtpc_inputAudioFileLocation, dc, id)); -} - -void AudioData::save(const QString &toFile) { - cancel(true); - loader = new mtpFileLoader(dc, id, access, mtpc_inputAudioFileLocation, toFile, size, (size < AudioVoiceMsgInMemory)); - loader->connect(loader, SIGNAL(progress(mtpFileLoader*)), App::main(), SLOT(audioLoadProgress(mtpFileLoader*))); - loader->connect(loader, SIGNAL(failed(mtpFileLoader*,bool)), App::main(), SLOT(audioLoadFailed(mtpFileLoader*,bool))); - loader->start(); -} - -QString AudioData::already(bool check) { - if (!check) return location.name; - if (!location.check()) location = Local::readFileLocation(mediaKey(mtpc_inputAudioFileLocation, dc, id)); - return location.name; -} - -void DocumentOpenLink::onClick(Qt::MouseButton button) const { - DocumentData *data = document(); - if (!data->date || button != Qt::LeftButton) return; - - QString already = data->already(true); - if (!already.isEmpty()) { - if (data->size < MediaViewImageSizeLimit) { - QImageReader reader(already); - if (reader.canRead()) { - if (reader.supportsAnimation() && reader.imageCount() > 1 && App::hoveredLinkItem()) { - startGif(App::hoveredLinkItem(), already); - } else { - App::wnd()->showDocument(data, QPixmap::fromImage(App::readImage(already, 0, false), Qt::ColorOnly), App::hoveredLinkItem()); - } - } else { - psOpenFile(already); - } - } else { - psOpenFile(already); - } - return; - } - - if (data->status != FileReady) return; - - QString name = data->name, filter; - MimeType mimeType = mimeTypeForName(data->mime); - QStringList p = mimeType.globPatterns(); - QString pattern = p.isEmpty() ? QString() : p.front(); - if (name.isEmpty()) { - name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString()); - } - - if (pattern.isEmpty()) { - filter = qsl("All files (*.*)"); - } else { - filter = mimeType.filterString() + qsl(";;All files (*.*)"); - } - - QString filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, false); - if (!filename.isEmpty()) { - data->openOnSave = 1; - data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0; - data->save(filename); - } -} - -void DocumentSaveLink::doSave(bool forceSavingAs) const { - DocumentData *data = document(); - if (!data->date) return; - - QString already = data->already(true); - if (!already.isEmpty() && !forceSavingAs) { - QPoint pos(QCursor::pos()); - if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { - psOpenFile(already, true); - } - } else { - QFileInfo alreadyInfo(already); - QDir alreadyDir(already.isEmpty() ? QDir() : alreadyInfo.dir()); - QString name = already.isEmpty() ? data->name : alreadyInfo.fileName(), filter; - MimeType mimeType = mimeTypeForName(data->mime); - QStringList p = mimeType.globPatterns(); - QString pattern = p.isEmpty() ? QString() : p.front(); - if (name.isEmpty()) { - name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString()); - } - - if (pattern.isEmpty()) { - filter = qsl("All files (*.*)"); - } else { - filter = mimeType.filterString() + qsl(";;All files (*.*)"); - } - - QString filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, forceSavingAs, alreadyDir); - if (!filename.isEmpty()) { - if (forceSavingAs) { - data->cancel(); - } else if (!already.isEmpty()) { - data->openOnSave = -1; - data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0; - } - data->save(filename); - } - } -} - -void DocumentSaveLink::onClick(Qt::MouseButton button) const { - if (button != Qt::LeftButton) return; - doSave(); -} - -void DocumentCancelLink::onClick(Qt::MouseButton button) const { - DocumentData *data = document(); - if (!data->date || button != Qt::LeftButton) return; - - data->cancel(); -} - -DocumentData::DocumentData(const DocumentId &id, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size) : -id(id), type(FileDocument), duration(0), access(access), date(date), mime(mime), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), openOnSaveMsgId(0), loader(0) { - setattributes(attributes); - location = Local::readFileLocation(mediaKey(mtpc_inputDocumentFileLocation, dc, id)); -} - -void DocumentData::setattributes(const QVector &attributes) { - for (int32 i = 0, l = attributes.size(); i < l; ++i) { - switch (attributes[i].type()) { - case mtpc_documentAttributeImageSize: { - const MTPDdocumentAttributeImageSize &d(attributes[i].c_documentAttributeImageSize()); - dimensions = QSize(d.vw.v, d.vh.v); - } break; - case mtpc_documentAttributeAnimated: if (type == FileDocument || type == StickerDocument) type = AnimatedDocument; break; - case mtpc_documentAttributeSticker: { - const MTPDdocumentAttributeSticker &d(attributes[i].c_documentAttributeSticker()); - if (type == FileDocument) type = StickerDocument; - alt = qs(d.valt); - } break; - case mtpc_documentAttributeVideo: { - const MTPDdocumentAttributeVideo &d(attributes[i].c_documentAttributeVideo()); - type = VideoDocument; - duration = d.vduration.v; - dimensions = QSize(d.vw.v, d.vh.v); - } break; - case mtpc_documentAttributeAudio: { - const MTPDdocumentAttributeAudio &d(attributes[i].c_documentAttributeAudio()); - type = AudioDocument; - duration = d.vduration.v; - } break; - case mtpc_documentAttributeFilename: name = qs(attributes[i].c_documentAttributeFilename().vfile_name); break; - } - } -} - -void DocumentData::save(const QString &toFile) { - cancel(true); - bool isSticker = (type == StickerDocument) && (dimensions.width() > 0) && (dimensions.height() > 0) && (size < StickerInMemory); - loader = new mtpFileLoader(dc, id, access, mtpc_inputDocumentFileLocation, toFile, size, isSticker); - loader->connect(loader, SIGNAL(progress(mtpFileLoader*)), App::main(), SLOT(documentLoadProgress(mtpFileLoader*))); - loader->connect(loader, SIGNAL(failed(mtpFileLoader*, bool)), App::main(), SLOT(documentLoadFailed(mtpFileLoader*, bool))); - loader->start(); -} - -QString DocumentData::already(bool check) { - if (!check) return location.name; - if (!location.check()) location = Local::readFileLocation(mediaKey(mtpc_inputDocumentFileLocation, dc, id)); - return location.name; -} - -void PeerLink::onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton && App::main()) { - App::main()->showPeerProfile(peer()); - } -} - -void MessageLink::onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton && App::main()) { - HistoryItem *current = App::mousedItem(); - if (current && current->history()->peer->id == peer()) { - App::main()->pushReplyReturn(current); - } - App::main()->showPeer(peer(), msgid()); - } -} - -MsgId clientMsgId() { - static MsgId current = -2000000000; - return ++current; -} - void DialogRow::paint(QPainter &p, int32 w, bool act, bool sel) const { QRect fullRect(0, 0, w, st::dlgHeight); p.fillRect(fullRect, (act ? st::dlgActiveBG : (sel ? st::dlgHoverBG : st::dlgBG))->b); @@ -1172,6 +589,7 @@ bool Histories::animStep(float64) { } Histories::Parent::iterator Histories::erase(Histories::Parent::iterator i) { + typing.remove(i.value()); delete i.value(); return Parent::erase(i); } @@ -1181,6 +599,7 @@ void Histories::remove(const PeerId &peer) { if (i != cend()) { erase(i); } + } HistoryItem *Histories::addToBack(const MTPmessage &msg, int msgState) { @@ -2194,7 +1613,7 @@ HistoryItem *regItem(HistoryItem *item, bool returnExisting) { return item; } -HistoryPhoto::HistoryPhoto(const MTPDphoto &photo, int32 width) : HistoryMedia(width) +HistoryPhoto::HistoryPhoto(const MTPDphoto &photo) : HistoryMedia() , pixw(1), pixh(1) , data(App::feedPhoto(photo)) , openl(new PhotoLink(data)) { @@ -2499,7 +1918,7 @@ QString formatDurationAndSizeText(qint64 duration, qint64 size) { int32 _downloadWidth = 0, _openWithWidth = 0, _cancelWidth = 0, _buttonWidth = 0; -HistoryVideo::HistoryVideo(const MTPDvideo &video, int32 width) : HistoryMedia(width) +HistoryVideo::HistoryVideo(const MTPDvideo &video) : HistoryMedia() , data(App::feedVideo(video)) , _openl(new VideoOpenLink(data)) , _savel(new VideoSaveLink(data)) @@ -2757,7 +2176,7 @@ ImagePtr HistoryVideo::replyPreview() { return data->replyPreview; } -HistoryAudio::HistoryAudio(const MTPDaudio &audio, int32 width) : HistoryMedia(width) +HistoryAudio::HistoryAudio(const MTPDaudio &audio) : HistoryMedia() , data(App::feedAudio(audio)) , _openl(new AudioOpenLink(data)) , _savel(new AudioSaveLink(data)) @@ -3004,7 +2423,7 @@ HistoryMedia *HistoryAudio::clone() const { return new HistoryAudio(*this); } -HistoryDocument::HistoryDocument(DocumentData *document, int32 width) : HistoryMedia(width) +HistoryDocument::HistoryDocument(DocumentData *document) : HistoryMedia() , data(document) , _openl(new DocumentOpenLink(data)) , _savel(new DocumentSaveLink(data)) @@ -3345,8 +2764,8 @@ ImagePtr HistoryDocument::replyPreview() { return data->replyPreview; } -HistorySticker::HistorySticker(DocumentData *document, int32 width) : HistoryMedia(width) -, pixw(1), pixh(1), data(document), lastw(width) +HistorySticker::HistorySticker(DocumentData *document) : HistoryMedia() +, pixw(1), pixh(1), data(document), lastw(0) { data->thumb->load(); updateStickerEmoji(); @@ -3724,6 +3143,423 @@ void HistoryContact::updateFrom(const MTPMessageMedia &media) { } } +HistoryWebPage::HistoryWebPage(WebPageData *data) : HistoryMedia() +, data(data) +, _openl(data->url.isEmpty() ? 0 : new TextLink(data->url)) +, _photol(data->photo ? new PhotoLink(data->photo) : 0) +, _asArticle(false) +, _title(st::msgMinWidth - st::webPageLeft) +, _description(st::msgMinWidth - st::webPageLeft) +, _siteNameWidth(0) +, _durationWidth(0) +, _pixw(0), _pixh(0) +{ +} + +void HistoryWebPage::initDimensions(const HistoryItem *parent) { + if (data->pendingTill) { + _maxw = st::webPageLeft + st::linkFont->m.width(lang((data->pendingTill < 0) ? lng_attach_failed : lng_profile_loading)); + _minh = st::replyHeight; + return; + } + if (!_openl && !data->url.isEmpty()) _openl = TextLinkPtr(new TextLink(data->url)); + if (data->photo && data->type != WebPagePhoto && data->type != WebPageVideo) { + if (data->type == WebPageProfile) { + _asArticle = true; + } else if (data->siteName == QLatin1String("Twitter") || data->siteName == QLatin1String("Facebook")) { + _asArticle = false; + } else { + _asArticle = true; + } + } else { + _asArticle = false; + } + if (_asArticle) { + w = st::webPagePhotoSize; + _maxw = st::webPageLeft + st::webPagePhotoSize; + _minh = st::webPagePhotoSize; + _minh += st::webPagePhotoSkip + (st::msgDateFont->height - st::msgDateDelta.y()); + } else if (data->photo) { + int32 tw = convertScale(data->photo->full->width()), th = convertScale(data->photo->full->height()); + if (!tw || !th) { + tw = th = 1; + } + if (tw > st::maxMediaSize) { + th = (st::maxMediaSize * th) / tw; + tw = st::maxMediaSize; + } + if (th > st::maxMediaSize) { + tw = (st::maxMediaSize * tw) / th; + th = st::maxMediaSize; + } + int32 thumbw = tw; + int32 thumbh = th; + + w = thumbw; + + _maxw = st::webPageLeft + qMax(w, int32(st::minPhotoSize)) + parent->timeWidth(); + _minh = qMax(thumbh, int32(st::minPhotoSize)); + _minh += st::webPagePhotoSkip; + } else { + _maxw = st::webPageLeft; + _minh = 0; + } + + if (!data->siteName.isEmpty()) { + _siteNameWidth = st::webPageTitleFont->m.width(data->siteName); + if (_asArticle) { + _maxw = qMax(_maxw, int32(st::webPageLeft + _siteNameWidth + st::webPagePhotoDelta + st::webPagePhotoSize)); + } else { + _maxw = qMax(_maxw, int32(st::webPageLeft + _siteNameWidth + parent->timeWidth())); + _minh += st::webPageTitleFont->height; + } + } + QString title(data->title.isEmpty() ? data->author : data->title); + if (!title.isEmpty()) { + _title.setText(st::webPageTitleFont, textClean(title), _webpageTitleOptions); + if (_asArticle) { + _maxw = qMax(_maxw, int32(st::webPageLeft + _title.maxWidth() + st::webPagePhotoDelta + st::webPagePhotoSize)); + } else { + _maxw = qMax(_maxw, int32(st::webPageLeft + _title.maxWidth() + parent->timeWidth())); + _minh += st::webPageTitleFont->height; + } + } + if (!data->description.isEmpty() && data->siteName != QLatin1String("YouTube")) { + _description.setText(st::webPageDescriptionFont, textClean(data->description), _webpageDescriptionOptions); + if (_asArticle) { + _maxw = qMax(_maxw, int32(st::webPageLeft + _description.maxWidth() + st::webPagePhotoDelta + st::webPagePhotoSize)); + } else { + _maxw = qMax(_maxw, int32(st::webPageLeft + _description.maxWidth() + parent->timeWidth())); + _minh += st::webPageTitleFont->height; + } + } + if (!_asArticle && data->photo && (_siteNameWidth || !_title.isEmpty() || !_description.isEmpty())) { + _minh += st::webPagePhotoSkip; + } + if (data->type == WebPageVideo && data->duration) { + _duration = formatDurationText(data->duration); + _durationWidth = st::msgDateFont->m.width(_duration); + } +} + +void HistoryWebPage::draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width) const { + if (width < 0) width = w; + if (width < 1) return; + + int32 bottomSkip = 0; + if (!data->pendingTill) { + if (data->photo) { + bottomSkip += st::webPagePhotoSkip; + if (_asArticle || (st::webPageLeft + qMax(_pixw, int16(st::minPhotoSize)) + parent->timeWidth() > width)) { + bottomSkip += (st::msgDateFont->height - st::msgDateDelta.y()); + } + } + } + + style::color bar = (selected ? (parent->out() ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (parent->out() ? st::msgOutReplyBarColor : st::msgInReplyBarColor)); + style::color semibold = (selected ? (parent->out() ? st::msgOutServiceSelColor : st::msgInServiceSelColor) : (parent->out() ? st::msgOutServiceColor : st::msgInServiceColor)); + style::color regular = (selected ? (parent->out() ? st::msgOutSelectDateColor : st::msgInSelectDateColor) : (parent->out() ? st::msgOutDateColor : st::msgInDateColor)); + p.fillRect(0, 0, st::webPageBar, _height - bottomSkip, bar->b); + + if (data->pendingTill) { + p.setFont(st::linkFont->f); + p.setPen(regular->p); + p.drawText(st::webPageLeft, (_minh - st::linkFont->height) / 2 + st::linkFont->ascent, lang(data->pendingTill < 0 ? lng_attach_failed : lng_profile_loading)); + return; + } + + p.save(); + p.translate(st::webPageLeft, 0); + + width -= st::webPageLeft; + + if (_asArticle) { + int32 pixwidth = st::webPagePhotoSize, pixheight = st::webPagePhotoSize; + data->photo->medium->load(false, false); + bool out = parent->out(); + bool full = data->photo->medium->loaded(); + QPixmap pix; + if (full) { + pix = data->photo->medium->pixSingle(_pixw, _pixh); + } else { + pix = data->photo->thumb->pixBlurredSingle(_pixw, _pixh); + } + if (_pixw < pixwidth || _pixh < pixheight) { + p.fillRect(QRect(width - pixwidth, 0, pixwidth, pixheight), st::black->b); + } + if (_pixw > pixwidth) { + p.drawPixmap(QRect(width - pixwidth, (pixheight - _pixh) / 2, pixwidth, _pixh), pix, QRect((_pixw - pixwidth) / 2, 0, pixwidth, _pixh)); + } else if (_pixh > pixheight) { + p.drawPixmap(QRect(width - pixwidth + (pixwidth - _pixw) / 2, 0, _pixw, pixheight), pix, QRect(0, (_pixh - pixheight) / 2, _pixw, pixheight)); + } else { + p.drawPixmap(QPoint(width - pixwidth + (pixwidth - _pixw) / 2, (pixheight - _pixh) / 2), pix); + } + } + int32 articleLines = 5; + if (_siteNameWidth) { + int32 availw = width; + if (_asArticle) { + availw -= st::webPagePhotoSize + st::webPagePhotoDelta; + } else if (_title.isEmpty() && _description.isEmpty() && !data->photo) { + availw -= parent->timeWidth(); + } + p.setFont(st::webPageTitleFont->f); + p.setPen(semibold->p); + p.drawText(0, st::webPageTitleFont->ascent, (availw >= _siteNameWidth) ? data->siteName : st::webPageTitleFont->m.elidedText(data->siteName, Qt::ElideRight, availw)); + p.translate(0, st::webPageTitleFont->height); + --articleLines; + } + if (!_title.isEmpty()) { + p.setPen(st::black->p); + int32 availw = width, endskip = 0; + if (_asArticle) { + availw -= st::webPagePhotoSize + st::webPagePhotoDelta; + } else if (_description.isEmpty() && !data->photo) { + endskip = parent->timeWidth(); + } + _title.drawElided(p, 0, 0, availw, 2, style::al_left, 0, -1, endskip); + int32 h = _title.countHeight(availw); + if (h > st::webPageTitleFont->height) { + articleLines -= 2; + p.translate(0, st::webPageTitleFont->height * 2); + } else { + --articleLines; + p.translate(0, h); + } + } + if (!_description.isEmpty()) { + p.setPen(st::black->p); + int32 availw = width, endskip = 0; + if (_asArticle) { + availw -= st::webPagePhotoSize + st::webPagePhotoDelta; + if (articleLines > 3) articleLines = 3; + } else if (!data->photo) { + endskip = parent->timeWidth(); + articleLines = 3; + } + _description.drawElided(p, 0, 0, availw, articleLines, style::al_left, 0, -1, endskip); + p.translate(0, qMin(_description.countHeight(availw), st::webPageDescriptionFont->height * articleLines)); + } + if (!_asArticle && data->photo) { + if (_siteNameWidth || !_title.isEmpty() || !_description.isEmpty()) { + p.translate(0, st::webPagePhotoSkip); + } + + int32 pixwidth = qMax(_pixw, int16(st::minPhotoSize)), pixheight = qMax(_pixh, int16(st::minPhotoSize)); + data->photo->full->load(false, false); + bool out = parent->out(); + bool full = data->photo->full->loaded(); + QPixmap pix; + if (full) { + pix = data->photo->full->pixSingle(_pixw, _pixh); + } else { + pix = data->photo->thumb->pixBlurredSingle(_pixw, _pixh); + } + if (_pixw < pixwidth || _pixh < pixheight) { + p.fillRect(QRect(0, 0, pixwidth, pixheight), st::black->b); + } + p.drawPixmap(QPoint((pixwidth - _pixw) / 2, (pixheight - _pixh) / 2), pix); + if (!full) { + uint64 dt = itemAnimations().animate(parent, getms()); + int32 cnt = int32(st::photoLoaderCnt), period = int32(st::photoLoaderPeriod), t = dt % period, delta = int32(st::photoLoaderDelta); + + int32 x = (pixwidth - st::photoLoader.width()) / 2, y = (pixheight - st::photoLoader.height()) / 2; + p.fillRect(x, y, st::photoLoader.width(), st::photoLoader.height(), st::photoLoaderBg->b); + x += (st::photoLoader.width() - cnt * st::photoLoaderPoint.width() - (cnt - 1) * st::photoLoaderSkip) / 2; + y += (st::photoLoader.height() - st::photoLoaderPoint.height()) / 2; + QColor c(st::white->c); + QBrush b(c); + for (int32 i = 0; i < cnt; ++i) { + t -= delta; + while (t < 0) t += period; + + float64 alpha = (t >= st::photoLoaderDuration1 + st::photoLoaderDuration2) ? 0 : ((t > st::photoLoaderDuration1 ? ((st::photoLoaderDuration1 + st::photoLoaderDuration2 - t) / st::photoLoaderDuration2) : (t / st::photoLoaderDuration1))); + c.setAlphaF(st::photoLoaderAlphaMin + alpha * (1 - st::photoLoaderAlphaMin)); + b.setColor(c); + p.fillRect(x + i * (st::photoLoaderPoint.width() + st::photoLoaderSkip), y, st::photoLoaderPoint.width(), st::photoLoaderPoint.height(), b); + } + } + + if (selected) { + p.fillRect(0, 0, pixwidth, pixheight, textstyleCurrent()->selectOverlay->b); + } + + if (data->type == WebPageVideo) { + if (data->siteName == QLatin1String("YouTube")) { + p.drawPixmap(QPoint((pixwidth - st::youtubeIcon.pxWidth()) / 2, (pixheight - st::youtubeIcon.pxHeight()) / 2), App::sprite(), st::youtubeIcon); + } else { + p.drawPixmap(QPoint((pixwidth - st::videoIcon.pxWidth()) / 2, (pixheight - st::videoIcon.pxHeight()) / 2), App::sprite(), st::videoIcon); + } + if (_durationWidth) { + int32 dateX = pixwidth - _durationWidth - st::msgDateImgDelta - 2 * st::msgDateImgPadding.x(); + int32 dateY = pixheight - st::msgDateFont->height - 2 * st::msgDateImgPadding.y() - st::msgDateImgDelta; + int32 dateW = pixwidth - dateX - st::msgDateImgDelta; + int32 dateH = pixheight - dateY - st::msgDateImgDelta; + + p.fillRect(dateX, dateY, dateW, dateH, st::msgDateImgBg->b); + if (selected) { + p.fillRect(dateX, dateY, dateW, dateH, textstyleCurrent()->selectOverlay->b); + } + p.setFont(st::msgDateFont->f); + p.setPen(st::msgDateImgColor->p); + p.drawText(dateX + st::msgDateImgPadding.x(), dateY + st::msgDateImgPadding.y() + st::msgDateFont->ascent, _duration); + } + } + + p.translate(0, pixheight); + } + + p.restore(); +} + +int32 HistoryWebPage::resize(int32 width, bool dontRecountText, const HistoryItem *parent) { + if (data->pendingTill) { + w = width; + _height = _minh; + return _height; + } + + w = width; + width -= st::webPageLeft; + if (_asArticle) { + int32 tw = convertScale(data->photo->medium->width()), th = convertScale(data->photo->medium->height()); + if (tw > st::webPagePhotoSize) { + if (th > tw) { + th = th * st::webPagePhotoSize / tw; + tw = st::webPagePhotoSize; + } else if (th > st::webPagePhotoSize) { + tw = tw * st::webPagePhotoSize / th; + th = st::webPagePhotoSize; + } + } + _pixw = tw; + _pixh = th; + if (_pixw < 1) _pixw = 1; + if (_pixh < 1) _pixh = 1; + _height = st::webPagePhotoSize; + _height += st::webPagePhotoSkip + (st::msgDateFont->height - st::msgDateDelta.y()); + } else if (data->photo) { + _pixw = qMin(width, int32(_maxw - st::webPageLeft)); + + int32 tw = convertScale(data->photo->full->width()), th = convertScale(data->photo->full->height()); + if (tw > st::maxMediaSize) { + th = (st::maxMediaSize * th) / tw; + tw = st::maxMediaSize; + } + if (th > st::maxMediaSize) { + tw = (st::maxMediaSize * tw) / th; + th = st::maxMediaSize; + } + _pixh = th; + if (tw > _pixw) { + _pixh = (_pixw * _pixh / tw); + } else { + _pixw = tw; + } + if (_pixh > width) { + _pixw = (_pixw * width) / _pixh; + _pixh = width; + } + if (_pixw < 1) _pixw = 1; + if (_pixh < 1) _pixh = 1; + _height = qMax(_pixh, int16(st::minPhotoSize)); + _height += st::webPagePhotoSkip; + if (qMax(_pixw, int16(st::minPhotoSize)) + parent->timeWidth() > width) { + _height += (st::msgDateFont->height - st::msgDateDelta.y()); + } + } else { + _height = 0; + } + + if (!_asArticle) { + if (!data->siteName.isEmpty()) { + _height += st::webPageTitleFont->height; + } + if (!_title.isEmpty()) { + _height += qMin(_title.countHeight(width), st::webPageTitleFont->height * 2); + } + if (!_description.isEmpty()) { + _height += qMin(_description.countHeight(width), st::webPageDescriptionFont->height * 3); + } + if (data->photo && (_siteNameWidth || !_title.isEmpty() || !_description.isEmpty())) { + _height += st::webPagePhotoSkip; + } + } + + return _height; +} + +void HistoryWebPage::regItem(HistoryItem *item) { + App::regWebPageItem(data, item); +} + +void HistoryWebPage::unregItem(HistoryItem *item) { + App::unregWebPageItem(data, item); +} + +const QString HistoryWebPage::inDialogsText() const { + return QString(); +} + +const QString HistoryWebPage::inHistoryText() const { + return QString(); +} + +bool HistoryWebPage::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { + if (width < 0) width = w; + if (width >= _maxw) width = _maxw; + + return (x >= 0 && y >= 0 && x < width && y < _height); +} + +TextLinkPtr HistoryWebPage::getLink(int32 x, int32 y, const HistoryItem *parent, int32 width) const { + if (width < 0) width = w; + if (width < 1) return TextLinkPtr(); + + TextLinkPtr result = (x >= 0 && y >= 0 && x < width && y < _height) ? _openl : TextLinkPtr(); + if (_photol) { + width -= st::webPageLeft; + + if (_siteNameWidth) { + y -= st::webPageTitleFont->height; + } + if (!_title.isEmpty()) { + y -= qMin(_title.countHeight(width), st::webPageTitleFont->height * 2); + } + if (!_description.isEmpty()) { + y -= qMin(_description.countHeight(width), st::webPageDescriptionFont->height * 3); + } + if (_siteNameWidth || !_title.isEmpty() || !_description.isEmpty()) { + y -= st::webPagePhotoSkip; + } + + int32 pixwidth = qMax(_pixw, int16(st::minPhotoSize)), pixheight = qMax(_pixh, int16(st::minPhotoSize)); + if (x >= 0 && y >= 0 && x < pixwidth && y < pixheight) { + return _photol; + } + } + return result; +} + +HistoryMedia *HistoryWebPage::clone() const { + return new HistoryWebPage(*this); +} + +ImagePtr HistoryWebPage::replyPreview() { + if (!data->photo) return ImagePtr(); + if (data->photo->replyPreview->isNull() && !data->photo->thumb->isNull()) { + if (data->photo->thumb->loaded()) { + int w = data->photo->thumb->width(), h = data->photo->thumb->height(); + if (w <= 0) w = 1; + if (h <= 0) h = 1; + data->photo->replyPreview = ImagePtr(w > h ? data->photo->thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : data->photo->thumb->pix(st::msgReplyBarSize.height()), "PNG"); + } else { + data->photo->thumb->load(); + } + } + return data->photo->replyPreview; +} + namespace { QRegularExpression reYouTube1(qsl("^(https?://)?(www\\.|m\\.)?youtube\\.com/watch\\?([^#]+&)?v=([a-z0-9_-]+)(&[^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption); QRegularExpression reYouTube2(qsl("^(https?://)?(www\\.)?youtu\\.be/([a-z0-9_-]+)([/\\?#][^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption); @@ -4075,7 +3911,7 @@ void ImageLinkData::load() { manager.getData(this); } -HistoryImageLink::HistoryImageLink(const QString &url, int32 width) : HistoryMedia(width) { +HistoryImageLink::HistoryImageLink(const QString &url) : HistoryMedia() { if (url.startsWith(qsl("location:"))) { QString lnk = qsl("https://maps.google.com/maps?q=") + url.mid(9) + qsl("&ll=") + url.mid(9) + qsl("&z=17"); link.reset(new TextLink(lnk)); @@ -4207,7 +4043,7 @@ void HistoryImageLink::draw(QPainter &p, const HistoryItem *parent, bool selecte if (data) { switch (data->type) { case YouTubeLink: p.drawPixmap(QPoint(skipx + (width - st::youtubeIcon.pxWidth()) / 2, skipy + (height - st::youtubeIcon.pxHeight()) / 2), App::sprite(), st::youtubeIcon); break; - case VimeoLink: p.drawPixmap(QPoint(skipx + (width - st::youtubeIcon.pxWidth()) / 2, skipy + (height - st::youtubeIcon.pxHeight()) / 2), App::sprite(), st::vimeoIcon); break; + case VimeoLink: p.drawPixmap(QPoint(skipx + (width - st::vimeoIcon.pxWidth()) / 2, skipy + (height - st::vimeoIcon.pxHeight()) / 2), App::sprite(), st::vimeoIcon); break; } if (!data->title.isEmpty() || !data->duration.isEmpty()) { p.fillRect(skipx, skipy, width, st::msgDateFont->height + 2 * st::msgDateImgPadding.y(), st::msgDateImgBg->b); @@ -4413,13 +4249,7 @@ HistoryItem(history, block, msgId, flags, date, from) void HistoryMessage::initMedia(const MTPMessageMedia &media, QString ¤tText) { switch (media.type()) { - case mtpc_messageMediaEmpty: { - QString lnk = currentText.trimmed(); - if (reYouTube1.match(currentText).hasMatch() || reYouTube2.match(currentText).hasMatch() || reInstagram.match(currentText).hasMatch() || reVimeo.match(currentText).hasMatch()) { - _media = new HistoryImageLink(lnk); - currentText = QString(); - } - } break; + case mtpc_messageMediaEmpty: initMediaFromText(currentText); break; case mtpc_messageMediaContact: { const MTPDmessageMediaContact &d(media.c_messageMediaContact()); _media = new HistoryContact(d.vuser_id.v, qs(d.vfirst_name), qs(d.vlast_name), qs(d.vphone_number)); @@ -4456,12 +4286,33 @@ void HistoryMessage::initMedia(const MTPMessageMedia &media, QString ¤tTex return initMediaFromDocument(doc); } } break; + case mtpc_messageMediaWebPage: { + const MTPWebPage &d(media.c_messageMediaWebPage().vwebpage); + switch (d.type()) { + case mtpc_webPageEmpty: initMediaFromText(currentText); break; + case mtpc_webPagePending: { + WebPageData *webPage = App::feedWebPage(d.c_webPagePending()); + _media = new HistoryWebPage(webPage); + } break; + case mtpc_webPage: { + _media = new HistoryWebPage(App::feedWebPage(d.c_webPage())); + } break; + } + } break; case mtpc_messageMediaUnsupported: default: currentText += " (unsupported media)"; break; }; if (_media) _media->regItem(this); } +void HistoryMessage::initMediaFromText(QString ¤tText) { + QString lnk = currentText.trimmed(); + if (/*reYouTube1.match(currentText).hasMatch() || reYouTube2.match(currentText).hasMatch() || reInstagram.match(currentText).hasMatch() || */reVimeo.match(currentText).hasMatch()) { + _media = new HistoryImageLink(lnk); + currentText = QString(); + } +} + void HistoryMessage::initMediaFromDocument(DocumentData *doc) { if (doc->type == StickerDocument && doc->dimensions.width() > 0 && doc->dimensions.height() > 0 && doc->dimensions.width() <= StickerMaxSize && doc->dimensions.height() <= StickerMaxSize && doc->size < StickerInMemory) { _media = new HistorySticker(doc); @@ -4474,21 +4325,31 @@ void HistoryMessage::initMediaFromDocument(DocumentData *doc) { void HistoryMessage::initDimensions(const QString &text) { _time = date.toString(cTimeFormat()); _timeWidth = st::msgDateFont->m.width(_time); - if (!_media) { + if (!_media || !text.isEmpty()) { // !justMedia() _timeWidth += st::msgDateSpace + (out() ? st::msgDateCheckSpace + st::msgCheckRect.pxWidth() : 0) - st::msgDateDelta.x(); - _text.setText(st::msgFont, text + textcmdSkipBlock(_timeWidth, st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions); + if (_media) { + _text.setText(st::msgFont, text, _historyTextOptions); + } else { + _text.setText(st::msgFont, text + textcmdSkipBlock(_timeWidth, st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions); + } } } void HistoryMessage::initDimensions(const HistoryItem *parent) { - if (_media) { + if (justMedia()) { _media->initDimensions(this); _maxw = _media->maxWidth(); - _minh = _media->height(); + _minh = _media->minHeight(); } else { _maxw = _text.maxWidth(); _minh = _text.minHeight(); _maxw += st::msgPadding.left() + st::msgPadding.right(); + if (_media) { + _media->initDimensions(this); + int32 maxw = _media->maxWidth() + st::msgPadding.left() + st::msgPadding.right(); + if (maxw > _maxw) _maxw = maxw; + _minh += st::msgPadding.bottom() + _media->minHeight(); + } } fromNameUpdated(); } @@ -4505,7 +4366,8 @@ bool HistoryMessage::uploading() const { QString HistoryMessage::selectedText(uint32 selection) const { if (_media && selection == FullItemSel) { - return _text.original(0, 0xFFFF) + _media->inHistoryText(); + QString text = _text.original(0, 0xFFFF), mediaText = _media->inHistoryText(); + return text.isEmpty() ? mediaText : (mediaText.isEmpty() ? text : (text + ' ' + mediaText)); } uint16 selectedFrom = (selection == FullItemSel) ? 0 : (selection >> 16) & 0xFFFF; uint16 selectedTo = (selection == FullItemSel) ? 0xFFFF : (selection & 0xFFFF); @@ -4513,13 +4375,31 @@ QString HistoryMessage::selectedText(uint32 selection) const { } QString HistoryMessage::inDialogsText() const { - return _media ? _media->inDialogsText() : _text.original(0, 0xFFFF, false); + QString result = _media ? _media->inDialogsText() : QString(); + return result.isEmpty() ? _text.original(0, 0xFFFF, false) : result; } HistoryMedia *HistoryMessage::getMedia(bool inOverview) const { return _media; } +void HistoryMessage::setMedia(const MTPmessageMedia &media) { + if (_media) return; + initMedia(media, QString()); + if (_media) { + QString was = HistoryMessage::selectedText(FullItemSel); + if (was.isEmpty()) { + _timeWidth -= st::msgDateSpace + (out() ? st::msgDateCheckSpace + st::msgCheckRect.pxWidth() : 0) - st::msgDateDelta.x(); + } else { + _text.setText(st::msgFont, was, _historyTextOptions); // without date skip + _textWidth = 0; + _textHeight = 0; + } + } + initDimensions(0); + if (App::main()) App::main()->itemResized(this); +} + void HistoryMessage::draw(QPainter &p, uint32 selection) const { textstyleSet(&(out() ? st::outTextStyle : st::inTextStyle)); @@ -4544,7 +4424,7 @@ void HistoryMessage::draw(QPainter &p, uint32 selection) const { _fromVersion = _from->nameVersion; } int32 left = out() ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (_media) { + if (justMedia()) { if (_media->maxWidth() > mwidth) mwidth = _media->maxWidth(); if (_media->currentWidth() < mwidth) mwidth = _media->currentWidth(); } @@ -4564,7 +4444,7 @@ void HistoryMessage::draw(QPainter &p, uint32 selection) const { if (out()) left += width - _maxw; width = _maxw; } - if (_media) { + if (justMedia()) { p.save(); p.translate(left, st::msgMargin.top()); _media->draw(p, this, selected); @@ -4587,6 +4467,12 @@ void HistoryMessage::draw(QPainter &p, uint32 selection) const { QRect trect(r.marginsAdded(-st::msgPadding)); drawMessageText(p, trect, selection); + if (_media) { + p.save(); + p.translate(left + st::msgPadding.left(), _height - st::msgMargin.bottom() - st::msgPadding.bottom() - _media->height()); + _media->draw(p, this, selected); + p.restore(); + } p.setFont(st::msgDateFont->f); style::color date(selected ? (out() ? st::msgOutSelectDateColor : st::msgInSelectDateColor) : (out() ? st::msgOutDateColor : st::msgInDateColor)); @@ -4594,7 +4480,7 @@ void HistoryMessage::draw(QPainter &p, uint32 selection) const { p.drawText(r.right() - st::msgPadding.right() + st::msgDateDelta.x() - _timeWidth + st::msgDateSpace, r.bottom() - st::msgPadding.bottom() + st::msgDateDelta.y() - st::msgDateFont->descent, _time); if (out()) { - QPoint iconPos(r.right() + 5 - st::msgPadding.right() - st::msgCheckRect.pxWidth(), r.bottom() + 1 - st::msgPadding.bottom() + st::msgDateDelta.y() - st::msgCheckRect.pxHeight()); + QPoint iconPos(r.right() + st::msgCheckPos.x() - st::msgPadding.right() - st::msgCheckRect.pxWidth(), r.bottom() + st::msgCheckPos.y() - st::msgPadding.bottom() + st::msgDateDelta.y() - st::msgCheckRect.pxHeight()); const QRect *iconRect; if (id > 0) { if (unread()) { @@ -4622,10 +4508,10 @@ void HistoryMessage::drawMessageText(QPainter &p, const QRect &trect, uint32 sel int32 HistoryMessage::resize(int32 width, bool dontRecountText, const HistoryItem *parent) { width -= st::msgMargin.left() + st::msgMargin.right(); - if (_media) { + if (justMedia()) { _height = _media->resize(width, dontRecountText, this); } else { - if (dontRecountText) return _height; + if (dontRecountText && !_media) return _height; if (width < st::msgPadding.left() + st::msgPadding.right() + 1) { width = st::msgPadding.left() + st::msgPadding.right() + 1; @@ -4639,8 +4525,10 @@ int32 HistoryMessage::resize(int32 width, bool dontRecountText, const HistoryIte } if (width >= _maxw) { _height = _minh; + if (_media) _media->resize(_maxw - st::msgPadding.left() - st::msgPadding.right(), dontRecountText, this); } else { _height = _textHeight; + if (_media) _height += st::msgPadding.bottom() + _media->resize(nwidth, dontRecountText, this); } if (!out() && _history->peer->chat) { _height += st::msgNameFont->height; @@ -4653,7 +4541,7 @@ int32 HistoryMessage::resize(int32 width, bool dontRecountText, const HistoryIte bool HistoryMessage::hasPoint(int32 x, int32 y) const { int32 left = out() ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (_media) { + if (justMedia()) { if (_media->maxWidth() > mwidth) mwidth = _media->maxWidth(); if (_media->currentWidth() < mwidth) mwidth = _media->currentWidth(); } @@ -4671,7 +4559,7 @@ bool HistoryMessage::hasPoint(int32 x, int32 y) const { if (out()) left += width - _maxw; width = _maxw; } - if (_media) { + if (justMedia()) { return _media->hasPoint(x - left, y - st::msgMargin.top(), this); } QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); @@ -4683,7 +4571,7 @@ void HistoryMessage::getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y) lnk = TextLinkPtr(); int32 left = out() ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (_media) { + if (justMedia()) { if (_media->maxWidth() > mwidth) mwidth = _media->maxWidth(); if (_media->currentWidth() < mwidth) mwidth = _media->currentWidth(); } @@ -4706,7 +4594,7 @@ void HistoryMessage::getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y) if (out()) left += width - _maxw; width = _maxw; } - if (_media) { + if (justMedia()) { lnk = _media->getLink(x - left, y - st::msgMargin.top(), this); return; } @@ -4719,14 +4607,22 @@ void HistoryMessage::getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y) r.setTop(r.top() + st::msgNameFont->height); } QRect trect(r.marginsAdded(-st::msgPadding)); + TextLinkPtr medialnk; + if (_media) { + if (y >= trect.bottom() - _media->height() && y < trect.bottom()) { + medialnk = _media->getLink(x - trect.left(), y + _media->height() - trect.bottom(), this); + } + trect.setBottom(trect.bottom() - _media->height() - st::msgPadding.bottom()); + } _text.getState(lnk, inText, x - trect.x(), y - trect.y(), trect.width()); + if (!lnk && medialnk) lnk = medialnk; } void HistoryMessage::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const { symbol = 0; after = false; upon = false; - if (_media) return; + if (justMedia()) return; int32 left = out() ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(); if (width > st::msgMaxWidth) { @@ -4749,6 +4645,9 @@ void HistoryMessage::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, r.setTop(r.top() + st::msgNameFont->height); } QRect trect(r.marginsAdded(-st::msgPadding)); + if (_media) { + trect.setBottom(trect.bottom() - _media->height() - st::msgPadding.bottom()); + } _text.getSymbol(symbol, after, upon, x - trect.x(), y - trect.y(), trect.width()); } @@ -4768,7 +4667,7 @@ void HistoryMessage::drawInDialog(QPainter &p, const QRect &r, bool act, const H if (r.width()) { textstyleSet(&(act ? st::dlgActiveTextStyle : st::dlgTextStyle)); p.setFont(st::dlgHistFont->f); - p.setPen((act ? st::dlgActiveColor : (_media ? st::dlgSystemColor : st::dlgTextColor))->p); + p.setPen((act ? st::dlgActiveColor : (justMedia() ? st::dlgSystemColor : st::dlgTextColor))->p); cache.drawElided(p, r.left(), r.top(), r.width(), r.height() / st::dlgHistFont->height); textstyleRestore(); } @@ -4796,8 +4695,8 @@ void HistoryMessage::updateStickerEmoji() { HistoryMessage::~HistoryMessage() { if (_media) { _media->unregItem(this); + delete _media; } - delete _media; } HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, const MTPDmessage &msg) : HistoryMessage(history, block, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.vfrom_id.v, textClean(qs(msg.vmessage)), msg.vmedia) @@ -4832,14 +4731,14 @@ void HistoryForwarded::initDimensions(const HistoryItem *parent) { } void HistoryForwarded::fwdNameUpdated() const { - if (_media) return; + if (justMedia()) return; fwdFromName.setText(st::msgServiceNameFont, App::peerName(fwdFrom), _textNameOptions); int32 _namew = fromWidth + fwdFromName.maxWidth() + st::msgPadding.left() + st::msgPadding.right(); if (_namew > _maxw) _maxw = _namew; } void HistoryForwarded::draw(QPainter &p, uint32 selection) const { - if (!_media && fwdFrom->nameVersion > fwdFromVersion) { + if (!justMedia() && fwdFrom->nameVersion > fwdFromVersion) { fwdNameUpdated(); fwdFromVersion = fwdFrom->nameVersion; } @@ -4870,15 +4769,14 @@ void HistoryForwarded::drawMessageText(QPainter &p, const QRect &trect, uint32 s int32 HistoryForwarded::resize(int32 width, bool dontRecountText, const HistoryItem *parent) { HistoryMessage::resize(width, dontRecountText, parent); - if (!_media && !dontRecountText) { - int32 h1 = 0, h2 = st::msgServiceNameFont->height; - _height += h1 + (h1 > h2 ? h1 : h2); + if (!justMedia() && (_media || !dontRecountText)) { + _height += st::msgServiceNameFont->height; } return _height; } bool HistoryForwarded::hasPoint(int32 x, int32 y) const { - if (!_media) { + if (!justMedia()) { int32 left = out() ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(); if (width > st::msgMaxWidth) { if (out()) left += width - st::msgMaxWidth; @@ -4905,7 +4803,7 @@ void HistoryForwarded::getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y lnk = TextLinkPtr(); inText = false; - if (!_media) { + if (!justMedia()) { int32 left = out() ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(); if (width > st::msgMaxWidth) { if (out()) left += width - st::msgMaxWidth; @@ -4935,14 +4833,13 @@ void HistoryForwarded::getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y } QRect trect(r.marginsAdded(-st::msgPadding)); - int32 h1 = 0, h2 = st::msgServiceNameFont->height; - if (y >= trect.top() + h1 && y < trect.top() + (h1 + h2)) { + if (y >= trect.top() && y < trect.top() + st::msgServiceNameFont->height) { if (x >= trect.left() + fromWidth && x < trect.right() && x < trect.left() + fromWidth + fwdFromName.maxWidth()) { lnk = fwdFrom->lnk; } return; } - y -= h1 + (h1 > h2 ? h1 : h2); + y -= st::msgServiceNameFont->height; } return HistoryMessage::getState(lnk, inText, x, y); } @@ -4952,7 +4849,7 @@ void HistoryForwarded::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 after = false; upon = false; - if (!_media) { + if (!justMedia()) { int32 left = out() ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(); if (width > st::msgMaxWidth) { if (out()) left += width - st::msgMaxWidth; @@ -4979,8 +4876,7 @@ void HistoryForwarded::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 } QRect trect(r.marginsAdded(-st::msgPadding)); - int32 h1 = 0, h2 = st::msgServiceNameFont->height; - y -= h1 + (h1 > h2 ? h1 : h2); + y -= st::msgServiceNameFont->height; } return HistoryMessage::getSymbol(symbol, after, upon, x, y); } @@ -5022,7 +4918,7 @@ void HistoryReply::initDimensions(const HistoryItem *parent) { HistoryMessage::initDimensions(parent); if (replyToMsg) { replyToNameUpdated(); - } else if (!_media) { + } else if (!justMedia()) { int maxw = _maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.left() + st::msgPadding.right(); if (maxw > _maxw) _maxw = maxw; } @@ -5058,7 +4954,7 @@ void HistoryReply::replyToNameUpdated() const { _maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgReplyPadding.right(); int32 _textw = st::msgReplyPadding.left() + st::msgReplyBarSkip + previewSkip + qMin(replyToText.maxWidth(), 4 * replyToName.maxWidth()) + st::msgReplyPadding.right(); if (_textw > _maxReplyWidth) _maxReplyWidth = _textw; - if (!_media) { + if (!justMedia()) { int maxw = _maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.left() + st::msgPadding.right(); if (maxw > _maxw) _maxw = maxw; } @@ -5168,14 +5064,14 @@ void HistoryReply::drawMessageText(QPainter &p, const QRect &trect, uint32 selec int32 HistoryReply::resize(int32 width, bool dontRecountText, const HistoryItem *parent) { HistoryMessage::resize(width, dontRecountText, parent); - if (!_media && !dontRecountText) { + if (!justMedia() && (_media || !dontRecountText)) { _height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); } return _height; } bool HistoryReply::hasPoint(int32 x, int32 y) const { - if (!_media) { + if (!justMedia()) { int32 left = out() ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(); if (width > st::msgMaxWidth) { if (out()) left += width - st::msgMaxWidth; @@ -5202,7 +5098,7 @@ void HistoryReply::getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y) co lnk = TextLinkPtr(); inText = false; - if (!_media) { + if (!justMedia()) { int32 left = out() ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(); if (width > st::msgMaxWidth) { if (out()) left += width - st::msgMaxWidth; @@ -5249,7 +5145,7 @@ void HistoryReply::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, i after = false; upon = false; - if (!_media) { + if (!justMedia()) { int32 left = out() ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(); if (width > st::msgMaxWidth) { if (out()) left += width - st::msgMaxWidth; diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 6e5896cf2b..6448b7d7c2 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -17,13 +17,6 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org */ #pragma once -typedef uint64 PeerId; -typedef uint64 PhotoId; -typedef uint64 VideoId; -typedef uint64 AudioId; -typedef uint64 DocumentId; -typedef int32 MsgId; - void historyInit(); class HistoryItem; @@ -39,467 +32,8 @@ typedef QMap SelectedItemSet; extern TextParseOptions _textNameOptions, _textDlgOptions; -struct NotifySettings { - NotifySettings() : mute(0), sound("default"), previews(true), events(1) { - } - int32 mute; - string sound; - bool previews; - int32 events; -}; -typedef NotifySettings *NotifySettingsPtr; +#include "structs.h" -static const NotifySettingsPtr UnknownNotifySettings = NotifySettingsPtr(0); -static const NotifySettingsPtr EmptyNotifySettings = NotifySettingsPtr(1); -extern NotifySettings globalNotifyAll, globalNotifyUsers, globalNotifyChats; -extern NotifySettingsPtr globalNotifyAllPtr, globalNotifyUsersPtr, globalNotifyChatsPtr; - -inline bool isNotifyMuted(NotifySettingsPtr settings) { - if (settings == UnknownNotifySettings || settings == EmptyNotifySettings) { - return false; - } - return (settings->mute > unixtime()); -} - -style::color peerColor(int32 index); -ImagePtr userDefPhoto(int32 index); -ImagePtr chatDefPhoto(int32 index); - -struct ChatData; -struct UserData; -struct PeerData { - PeerData(const PeerId &id); - virtual ~PeerData() { - if (notify != UnknownNotifySettings && notify != EmptyNotifySettings) { - delete notify; - notify = UnknownNotifySettings; - } - } - - UserData *asUser(); - const UserData *asUser() const; - - ChatData *asChat(); - const ChatData *asChat() const; - - void updateName(const QString &newName, const QString &newNameOrPhone, const QString &newUsername); - - void fillNames(); - - virtual void nameUpdated() { - } - - PeerId id; - - QString name; - QString nameOrPhone; - typedef QSet Names; - Names names; // for filtering - typedef QSet NameFirstChars; - NameFirstChars chars; - - bool loaded; - bool chat; - uint64 access; - MTPinputPeer input; - MTPinputUser inputUser; - - int32 colorIndex; - style::color color; - ImagePtr photo; - - int32 nameVersion; - - NotifySettingsPtr notify; -}; - -class PeerLink : public ITextLink { -public: - PeerLink(PeerData *peer) : _peer(peer) { - } - void onClick(Qt::MouseButton button) const; - PeerData *peer() const { - return _peer; - } - -private: - PeerData *_peer; -}; - -struct PhotoData; -struct UserData : public PeerData { - UserData(const PeerId &id) : PeerData(id), lnk(new PeerLink(this)), onlineTill(0), contact(-1), photosCount(-1) { - } - void setPhoto(const MTPUserProfilePhoto &photo); - void setName(const QString &first, const QString &last, const QString &phoneName, const QString &username); - void setPhone(const QString &newPhone); - void nameUpdated(); - - QString firstName; - QString lastName; - QString username; - QString phone; - Text nameText; - PhotoId photoId; - TextLinkPtr lnk; - int32 onlineTill; - int32 contact; // -1 - not contact, cant add (self, empty, deleted, foreign), 0 - not contact, can add (request), 1 - contact - - typedef QList Photos; - Photos photos; - int32 photosCount; // -1 not loaded, 0 all loaded -}; - -struct ChatData : public PeerData { - ChatData(const PeerId &id) : PeerData(id), count(0), date(0), version(0), left(false), forbidden(true), photoId(0) { - } - void setPhoto(const MTPChatPhoto &photo, const PhotoId &phId = 0); - int32 count; - int32 date; - int32 version; - int32 admin; - bool left; - bool forbidden; - typedef QMap Participants; - Participants participants; - typedef QMap CanKick; - CanKick cankick; - typedef QList LastAuthors; - LastAuthors lastAuthors; - ImagePtr photoFull; - PhotoId photoId; - // geo -}; - -typedef QMap PreparedPhotoThumbs; -struct PhotoData { - PhotoData(const PhotoId &id, const uint64 &access = 0, int32 user = 0, int32 date = 0, const ImagePtr &thumb = ImagePtr(), const ImagePtr &medium = ImagePtr(), const ImagePtr &full = ImagePtr()) : - id(id), access(access), user(user), date(date), thumb(thumb), medium(medium), full(full), chat(0) { - } - void forget() { - thumb->forget(); - replyPreview->forget(); - medium->forget(); - full->forget(); - } - PhotoId id; - uint64 access; - int32 user; - int32 date; - ImagePtr thumb, replyPreview; - ImagePtr medium; - ImagePtr full; - ChatData *chat; // for chat photos connection - // geo, caption - - int32 cachew; - QPixmap cache; -}; - -class PhotoLink : public ITextLink { -public: - PhotoLink(PhotoData *photo) : _photo(photo), _peer(0) { - } - PhotoLink(PhotoData *photo, PeerData *peer) : _photo(photo), _peer(peer) { - } - void onClick(Qt::MouseButton button) const; - PhotoData *photo() const { - return _photo; - } - PeerData *peer() const { - return _peer; - } - -private: - PhotoData *_photo; - PeerData *_peer; -}; - -enum FileStatus { - FileFailed = -1, - FileUploading = 0, - FileReady = 1, -}; - -struct VideoData { - VideoData(const VideoId &id, const uint64 &access = 0, int32 user = 0, int32 date = 0, int32 duration = 0, int32 w = 0, int32 h = 0, const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0); - - void forget() { - thumb->forget(); - replyPreview->forget(); - } - - void save(const QString &toFile); - - void cancel(bool beforeDownload = false) { - mtpFileLoader *l = loader; - loader = 0; - if (l) { - l->cancel(); - l->deleteLater(); - l->rpcInvalidate(); - } - location = FileLocation(); - if (!beforeDownload) { - openOnSave = openOnSaveMsgId = 0; - } - } - - void finish() { - if (loader->done()) { - location = FileLocation(loader->fileType(), loader->fileName()); - } - loader->deleteLater(); - loader->rpcInvalidate(); - loader = 0; - } - - QString already(bool check = false); - - VideoId id; - uint64 access; - int32 user; - int32 date; - int32 duration; - int32 w, h; - ImagePtr thumb, replyPreview; - int32 dc, size; - // geo, caption - - FileStatus status; - int32 uploadOffset; - - mtpTypeId fileType; - int32 openOnSave, openOnSaveMsgId; - mtpFileLoader *loader; - FileLocation location; -}; - -class VideoLink : public ITextLink { -public: - VideoLink(VideoData *video) : _video(video) { - } - VideoData *video() const { - return _video; - } - -private: - VideoData *_video; -}; - -class VideoSaveLink : public VideoLink { -public: - VideoSaveLink(VideoData *video) : VideoLink(video) { - } - void doSave(bool forceSavingAs = false) const; - void onClick(Qt::MouseButton button) const; -}; - -class VideoOpenLink : public VideoLink { -public: - VideoOpenLink(VideoData *video) : VideoLink(video) { - } - void onClick(Qt::MouseButton button) const; -}; - -class VideoCancelLink : public VideoLink { -public: - VideoCancelLink(VideoData *video) : VideoLink(video) { - } - void onClick(Qt::MouseButton button) const; -}; - -struct AudioData { - AudioData(const AudioId &id, const uint64 &access = 0, int32 user = 0, int32 date = 0, const QString &mime = QString(), int32 duration = 0, int32 dc = 0, int32 size = 0); - - void forget() { - } - - void save(const QString &toFile); - - void cancel(bool beforeDownload = false) { - mtpFileLoader *l = loader; - loader = 0; - if (l) { - l->cancel(); - l->deleteLater(); - l->rpcInvalidate(); - } - location = FileLocation(); - if (!beforeDownload) { - openOnSave = openOnSaveMsgId = 0; - } - } - - void finish() { - if (loader->done()) { - location = FileLocation(loader->fileType(), loader->fileName()); - data = loader->bytes(); - } - loader->deleteLater(); - loader->rpcInvalidate(); - loader = 0; - } - - QString already(bool check = false); - - AudioId id; - uint64 access; - int32 user; - int32 date; - QString mime; - int32 duration; - int32 dc; - int32 size; - - FileStatus status; - int32 uploadOffset; - - int32 openOnSave, openOnSaveMsgId; - mtpFileLoader *loader; - FileLocation location; - QByteArray data; - int32 md5[8]; -}; - -class AudioLink : public ITextLink { -public: - AudioLink(AudioData *audio) : _audio(audio) { - } - AudioData *audio() const { - return _audio; - } - -private: - AudioData *_audio; -}; - -class AudioSaveLink : public AudioLink { -public: - AudioSaveLink(AudioData *audio) : AudioLink(audio) { - } - void doSave(bool forceSavingAs = false) const; - void onClick(Qt::MouseButton button) const; -}; - -class AudioOpenLink : public AudioLink { -public: - AudioOpenLink(AudioData *audio) : AudioLink(audio) { - } - void onClick(Qt::MouseButton button) const; -}; - -class AudioCancelLink : public AudioLink { -public: - AudioCancelLink(AudioData *audio) : AudioLink(audio) { - } - void onClick(Qt::MouseButton button) const; -}; - -enum DocumentType { - FileDocument, - VideoDocument, - AudioDocument, - StickerDocument, - AnimatedDocument -}; -struct DocumentData { - DocumentData(const DocumentId &id, const uint64 &access = 0, int32 date = 0, const QVector &attributes = QVector(), const QString &mime = QString(), const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0); - void setattributes(const QVector &attributes); - - void forget() { - thumb->forget(); - sticker->forget(); - replyPreview->forget(); - } - - void save(const QString &toFile); - - void cancel(bool beforeDownload = false) { - mtpFileLoader *l = loader; - loader = 0; - if (l) { - l->cancel(); - l->deleteLater(); - l->rpcInvalidate(); - } - location = FileLocation(); - if (!beforeDownload) { - openOnSave = openOnSaveMsgId = 0; - } - } - - void finish() { - if (loader->done()) { - location = FileLocation(loader->fileType(), loader->fileName()); - data = loader->bytes(); - } - loader->deleteLater(); - loader->rpcInvalidate(); - loader = 0; - } - - QString already(bool check = false); - - DocumentId id; - DocumentType type; - QSize dimensions; - int32 duration; - uint64 access; - int32 date; - QString name, mime, alt; // alt - for stickers - ImagePtr thumb, replyPreview; - int32 dc; - int32 size; - - FileStatus status; - int32 uploadOffset; - - int32 openOnSave, openOnSaveMsgId; - mtpFileLoader *loader; - FileLocation location; - - QByteArray data; - ImagePtr sticker; - - int32 md5[8]; -}; - -class DocumentLink : public ITextLink { -public: - DocumentLink(DocumentData *document) : _document(document) { - } - DocumentData *document() const { - return _document; - } - -private: - DocumentData *_document; -}; - -class DocumentSaveLink : public DocumentLink { -public: - DocumentSaveLink(DocumentData *document) : DocumentLink(document) { - } - void doSave(bool forceSavingAs = false) const; - void onClick(Qt::MouseButton button) const; -}; - -class DocumentOpenLink : public DocumentLink { -public: - DocumentOpenLink(DocumentData *document) : DocumentLink(document) { - } - void onClick(Qt::MouseButton button) const; -}; - -class DocumentCancelLink : public DocumentLink { -public: - DocumentCancelLink(DocumentData *document) : DocumentLink(document) { - } - void onClick(Qt::MouseButton button) const; -}; - -MsgId clientMsgId(); struct History; struct Histories : public QHash, public Animated { @@ -521,7 +55,7 @@ struct Histories : public QHash, public Animated { } HistoryItem *addToBack(const MTPmessage &msg, int msgState = 1); // 2 - new read message, 1 - new unread message, 0 - not new message, -1 - searched message -// HistoryItem *addToBack(const MTPgeoChatMessage &msg, bool newMsg = true); + // HistoryItem *addToBack(const MTPgeoChatMessage &msg, bool newMsg = true); typedef QMap TypingHistories; // when typing in this history started TypingHistories typing; @@ -563,6 +97,7 @@ enum HistoryMediaType { MediaTypeDocument, MediaTypeSticker, MediaTypeImageLink, + MediaTypeWebPage, MediaTypeCount }; @@ -581,7 +116,7 @@ inline MediaOverviewType mediaToOverviewType(HistoryMediaType t) { case MediaTypePhoto: return OverviewPhotos; case MediaTypeVideo: return OverviewVideos; case MediaTypeDocument: return OverviewDocuments; -// case MediaTypeSticker: return OverviewDocuments; + // case MediaTypeSticker: return OverviewDocuments; case MediaTypeAudio: return OverviewAudios; } return OverviewCount; @@ -598,34 +133,6 @@ inline MTPMessagesFilter typeToMediaFilter(MediaOverviewType &type) { return MTPMessagesFilter(); } -struct MessageCursor { - MessageCursor() : position(0), anchor(0), scroll(QFIXED_MAX) { - } - MessageCursor(int position, int anchor, int scroll) : position(position), anchor(anchor), scroll(scroll) { - } - MessageCursor(const QTextEdit &edit) { - fillFrom(edit); - } - void fillFrom(const QTextEdit &edit) { - QTextCursor c = edit.textCursor(); - position = c.position(); - anchor = c.anchor(); - QScrollBar *s = edit.verticalScrollBar(); - scroll = s ? s->value() : QFIXED_MAX; - } - void applyTo(QTextEdit &edit, bool *lock = 0) { - if (lock) *lock = true; - QTextCursor c = edit.textCursor(); - c.setPosition(anchor, QTextCursor::MoveAnchor); - c.setPosition(position, QTextCursor::KeepAnchor); - edit.setTextCursor(c); - QScrollBar *s = edit.verticalScrollBar(); - if (s) s->setValue(scroll); - if (lock) *lock = false; - } - int position, anchor, scroll; -}; - class HistoryMedia; class HistoryMessage; class HistoryUnreadBar; @@ -1084,6 +591,9 @@ public: int32 maxWidth() const { return _maxw; } + int32 minHeight() const { + return _minh; + } virtual ~HistoryElem() { } @@ -1206,6 +716,8 @@ public: virtual HistoryMedia *getMedia(bool inOverview = false) const { return 0; } + virtual void setMedia(const MTPmessageMedia &media) { + } virtual QString time() const { return QString(); } @@ -1259,6 +771,8 @@ public: HistoryMedia(int32 width = 0) : w(width) { } + HistoryMedia(const HistoryMedia &other) : w(0) { + } virtual HistoryMediaType type() const = 0; virtual const QString inDialogsText() const = 0; @@ -1315,7 +829,7 @@ protected: class HistoryPhoto : public HistoryMedia { public: - HistoryPhoto(const MTPDphoto &photo, int32 width = 0); + HistoryPhoto(const MTPDphoto &photo); HistoryPhoto(PeerData *chat, const MTPDphoto &photo, int32 width = 0); void init(); @@ -1364,7 +878,7 @@ QString formatSizeText(qint64 size); class HistoryVideo : public HistoryMedia { public: - HistoryVideo(const MTPDvideo &video, int32 width = 0); + HistoryVideo(const MTPDvideo &video); void initDimensions(const HistoryItem *parent); void draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width = -1) const; @@ -1402,7 +916,7 @@ private: class HistoryAudio : public HistoryMedia { public: - HistoryAudio(const MTPDaudio &audio, int32 width = 0); + HistoryAudio(const MTPDaudio &audio); void initDimensions(const HistoryItem *parent); void draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width = -1) const; @@ -1434,7 +948,7 @@ private: class HistoryDocument : public HistoryMedia { public: - HistoryDocument(DocumentData *document, int32 width = 0); + HistoryDocument(DocumentData *document); void initDimensions(const HistoryItem *parent); void draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width = -1) const; @@ -1482,7 +996,7 @@ private: class HistorySticker : public HistoryMedia { public: - HistorySticker(DocumentData *document, int32 width = 0); + HistorySticker(DocumentData *document); void initDimensions(const HistoryItem *parent); void draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width = -1) const; @@ -1542,6 +1056,45 @@ private: UserData *contact; }; +class HistoryWebPage : public HistoryMedia { +public: + + HistoryWebPage(WebPageData *data); + void initDimensions(const HistoryItem *parent); + + void draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width = -1) const; + int32 resize(int32 width, bool dontRecountText = false, const HistoryItem *parent = 0); + HistoryMediaType type() const { + return MediaTypeWebPage; + } + const QString inDialogsText() const; + const QString inHistoryText() const; + bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; + TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; + HistoryMedia *clone() const; + + void regItem(HistoryItem *item); + void unregItem(HistoryItem *item); + + bool hasReplyPreview() const { + return data->photo && !data->photo->thumb->isNull(); + } + ImagePtr replyPreview(); + +private: + WebPageData *data; + TextLinkPtr _openl, _photol; + bool _asArticle; + + Text _title, _description; + int32 _siteNameWidth; + + QString _duration; + int32 _durationWidth; + + int16 _pixw, _pixh; +}; + void initImageLinkManager(); void reinitImageLinkManager(); void deinitImageLinkManager(); @@ -1597,7 +1150,7 @@ private: class HistoryImageLink : public HistoryMedia { public: - HistoryImageLink(const QString &url, int32 width = 0); + HistoryImageLink(const QString &url); int32 fullWidth() const; int32 fullHeight() const; void initDimensions(const HistoryItem *parent); @@ -1628,11 +1181,16 @@ public: HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, QDateTime date, int32 from, DocumentData *doc); void initMedia(const MTPMessageMedia &media, QString ¤tText); + void initMediaFromText(QString ¤tText); void initMediaFromDocument(DocumentData *doc); void initDimensions(const HistoryItem *parent = 0); void initDimensions(const QString &text); void fromNameUpdated() const; + bool justMedia() const { + return _media && _text.isEmpty(); + } + bool uploading() const; void draw(QPainter &p, uint32 selection) const; @@ -1658,6 +1216,7 @@ public: QString selectedText(uint32 selection) const; QString inDialogsText() const; HistoryMedia *getMedia(bool inOverview = false) const; + void setMedia(const MTPmessageMedia &media); QString time() const { return _time; @@ -1731,6 +1290,7 @@ public: HistoryReply(History *history, HistoryBlock *block, MsgId msgId, int32 flags, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc); void initDimensions(const HistoryItem *parent = 0); + bool updateReplyTo(bool force = false); void replyToNameUpdated() const; int32 replyToWidth() const; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 93094a715e..7e0a35f91d 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -1292,7 +1292,7 @@ void MessageField::insertFromMimeData(const QMimeData *source) { if (source->hasImage()) { QImage img = qvariant_cast(source->imageData()); if (!img.isNull()) { - history->uploadImage(img); + history->uploadImage(img, false, source->text()); return; } } @@ -1385,6 +1385,10 @@ bool HistoryHider::animStep(float64 ms) { return res; } +bool HistoryHider::withConfirm() const { + return _sharedContact || _sendPath; +} + void HistoryHider::paintEvent(QPaintEvent *e) { QPainter p(this); if (!hiding || !cacheForAnim.isNull() || !offered) { @@ -1470,6 +1474,7 @@ void HistoryHider::forward() { parent()->onForward(offered->id, _forwardSelected); } } + emit forwarded(); } void HistoryHider::forwardDone() { @@ -1903,6 +1908,8 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { } bool HistoryWidget::stickersFailed(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + _lastStickersUpdate = getms(true); _stickersUpdateRequest = 0; return true; @@ -2258,8 +2265,10 @@ void HistoryWidget::historyWasRead(bool force) { App::main()->readServerHistory(hist, force); } -bool HistoryWidget::messagesFailed(const RPCError &e, mtpRequestId requestId) { - LOG(("RPC Error: %1 %2: %3").arg(e.code()).arg(e.type()).arg(e.description())); +bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + + LOG(("RPC Error: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description())); if (histPreloading == requestId) { histPreloading = 0; } else if (histPreloadingDown == requestId) { @@ -2587,11 +2596,6 @@ void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const h->sendRequestId = MTP::send(MTPmessages_SendMedia(p->input, MTP_int(replyTo), MTP_inputMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname)), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); App::historyRegRandom(randomId, newId); - if (hist && histPeer && peer == histPeer->id) { - App::main()->historyToDown(hist); - } - App::main()->dialogsToUp(); - peerMessagesUpdated(peer); App::main()->finishForwarding(h); cancelReply(); @@ -2613,7 +2617,7 @@ PeerData *HistoryWidget::activePeer() const { } MsgId HistoryWidget::activeMsgId() const { - return hist ? hist->activeMsgId : (_activeHist ? _activeHist->activeMsgId : 0); + return (_loadingAroundId >= 0) ? _loadingAroundId : (hist ? hist->activeMsgId : (_activeHist ? _activeHist->activeMsgId : 0)); } int32 HistoryWidget::lastWidth() const { @@ -3046,12 +3050,13 @@ void HistoryWidget::onFieldCursorChanged() { onDraftSaveDelayed(); } -void HistoryWidget::uploadImage(const QImage &img, bool withText) { +void HistoryWidget::uploadImage(const QImage &img, bool withText, const QString &source) { if (!hist || confirmImageId) return; App::wnd()->activateWindow(); confirmImage = img; confirmWithText = withText; + confirmSource = source; confirmImageId = imageLoader.append(img, histPeer->id, _replyToId, ToPreparePhoto); } @@ -3109,7 +3114,10 @@ void HistoryWidget::onPhotoReady() { for (ReadyLocalMedias::const_iterator i = list.cbegin(), e = list.cend(); i != e; ++i) { if (i->id == confirmImageId) { - App::wnd()->showLayer(new PhotoSendBox(*i)); + PhotoSendBox *box = new PhotoSendBox(*i); + connect(box, SIGNAL(confirmed()), this, SLOT(onSendConfirmed())); + connect(box, SIGNAL(destroyed(QObject*)), this, SLOT(onSendCancelled())); + App::wnd()->showLayer(box); } else { confirmSendImage(*i); } @@ -3117,6 +3125,17 @@ void HistoryWidget::onPhotoReady() { list.clear(); } +void HistoryWidget::onSendConfirmed() { + if (!confirmSource.isEmpty()) confirmSource = QString(); +} + +void HistoryWidget::onSendCancelled() { + if (!confirmSource.isEmpty()) { + _field.textCursor().insertText(confirmSource); + confirmSource = QString(); + } +} + void HistoryWidget::onPhotoFailed(quint64 id) { } @@ -3341,8 +3360,8 @@ void HistoryWidget::itemReplaced(HistoryItem *oldItem, HistoryItem *newItem) { if (_replyReturn == oldItem) _replyReturn = newItem; } -void HistoryWidget::itemResized(HistoryItem *row) { - updateListSize(0, false, false, row); +void HistoryWidget::itemResized(HistoryItem *row, bool scrollToIt) { + updateListSize(0, false, false, row, scrollToIt); } void HistoryWidget::updateScrollColors() { @@ -3354,7 +3373,7 @@ MsgId HistoryWidget::replyToId() const { return _replyToId; } -void HistoryWidget::updateListSize(int32 addToY, bool initial, bool loadedDown, HistoryItem *resizedItem) { +void HistoryWidget::updateListSize(int32 addToY, bool initial, bool loadedDown, HistoryItem *resizedItem, bool scrollToIt) { if (!hist || (!_histInited && !initial)) return; if (!isVisible()) { @@ -3382,7 +3401,7 @@ void HistoryWidget::updateListSize(int32 addToY, bool initial, bool loadedDown, _scroll.show(); } _list->updateSize(); - if (resizedItem && !resizedItem->detached()) { + if (resizedItem && !resizedItem->detached() && scrollToIt) { int32 firstItemY = _list->height() - hist->height - st::historyPadding; if (newSt + _scroll.height() < firstItemY + resizedItem->block()->y + resizedItem->y + resizedItem->height()) { newSt = firstItemY + resizedItem->block()->y + resizedItem->y + resizedItem->height() - _scroll.height(); diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index b809f62710..8ae2353e89 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -197,6 +197,7 @@ public: HistoryHider(MainWidget *parent); // send path from command line argument bool animStep(float64 ms); + bool withConfirm() const; void paintEvent(QPaintEvent *e); void keyPressEvent(QKeyEvent *e); @@ -217,6 +218,10 @@ public slots: void startHide(); void forward(); +signals: + + void forwarded(); + private: void init(); @@ -296,7 +301,7 @@ public: void typingDone(const MTPBool &result, mtpRequestId req); void destroyData(); - void uploadImage(const QImage &img, bool withText = false); + void uploadImage(const QImage &img, bool withText = false, const QString &source = QString()); void uploadFile(const QString &file, bool withText = false); // with confirmation void shareContactConfirmation(const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, bool withText = false); void uploadConfirmImageUncompressed(bool ctrlShiftEnter, MsgId replyTo); @@ -342,7 +347,7 @@ public: void fillSelectedItems(SelectedItemSet &sel, bool forDelete = true); void itemRemoved(HistoryItem *item); void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem); - void itemResized(HistoryItem *item); + void itemResized(HistoryItem *item, bool scrollToIt); void updateScrollColors(); @@ -394,6 +399,8 @@ public slots: void onDocumentDrop(QDropEvent *e); void onPhotoReady(); + void onSendConfirmed(); + void onSendCancelled(); void onPhotoFailed(quint64 id); void showPeer(const PeerId &peer, MsgId msgId = 0, bool force = false, bool leaveActive = false); void clearLoadingAround(); @@ -440,7 +447,7 @@ private: QList _replyReturns; bool messagesFailed(const RPCError &error, mtpRequestId requestId); - void updateListSize(int32 addToY = 0, bool initial = false, bool loadedDown = false, HistoryItem *resizedItem = 0); + void updateListSize(int32 addToY = 0, bool initial = false, bool loadedDown = false, HistoryItem *resizedItem = 0, bool scrollToIt = false); void addMessagesToFront(const QVector &messages); void addMessagesToBack(const QVector &messages); @@ -497,6 +504,7 @@ private: QImage confirmImage; PhotoId confirmImageId; bool confirmWithText; + QString confirmSource; QString titlePeerText; int32 titlePeerTextWidth; diff --git a/Telegram/SourceFiles/intro/intro.cpp b/Telegram/SourceFiles/intro/intro.cpp index 67ec44e3fa..0a6bace091 100644 --- a/Telegram/SourceFiles/intro/intro.cpp +++ b/Telegram/SourceFiles/intro/intro.cpp @@ -107,7 +107,7 @@ void IntroWidget::onParentResize(const QSize &newSize) { void IntroWidget::onIntroBack() { if (!current) return; - moving = -1; + moving = (current == 4) ? -2 : -1; prepareMove(); } @@ -125,11 +125,13 @@ bool IntroWidget::createNext() { case 1: stages[current + 1] = code = new IntroCode(this); break; case 2: if (_pwdSalt.isEmpty()) { + if (signup) delete signup; stages[current + 1] = signup = new IntroSignup(this); } else { stages[current + 1] = pwdcheck = new IntroPwdCheck(this); } break; + case 3: stages[current + 1] = signup = new IntroSignup(this); break; } } _back.raise(); @@ -138,11 +140,14 @@ bool IntroWidget::createNext() { void IntroWidget::prepareMove() { if (cacheForHide.isNull() || cacheForHideInd != current) makeHideCache(); + + stages[current + moving]->prepareShow(); if (cacheForShow.isNull() || cacheForShowInd != current + moving) makeShowCache(); - xCoordHide = anim::ivalue(0, -moving * st::introSlideShift); + int32 m = (moving > 0) ? 1 : -1; + xCoordHide = anim::ivalue(0, -m * st::introSlideShift); cAlphaHide = anim::fvalue(1, 0); - xCoordShow = anim::ivalue(moving * st::introSlideShift, 0); + xCoordShow = anim::ivalue(m * st::introSlideShift, 0); cAlphaShow = anim::fvalue(0, 1); anim::start(this); @@ -315,7 +320,7 @@ void IntroWidget::setPwdSalt(const QByteArray &salt) { _pwdSalt = salt; delete signup; delete pwdcheck; - stages[3] = 0; + stages[3] = stages[4] = 0; signup = 0; pwdcheck = 0; } diff --git a/Telegram/SourceFiles/intro/intro.h b/Telegram/SourceFiles/intro/intro.h index 466c92563b..dd59066a5a 100644 --- a/Telegram/SourceFiles/intro/intro.h +++ b/Telegram/SourceFiles/intro/intro.h @@ -104,7 +104,7 @@ private: IntroCode *code; IntroSignup *signup; IntroPwdCheck *pwdcheck; - IntroStage *stages[4]; + IntroStage *stages[5]; int current, moving, visibilityChanging; QString _phone, _phone_hash; @@ -131,6 +131,8 @@ public: } virtual void activate() = 0; // show and activate + virtual void prepareShow() { + } virtual void deactivate() = 0; // deactivate and hide virtual void onNext() = 0; virtual void onBack() = 0; diff --git a/Telegram/SourceFiles/intro/introcode.cpp b/Telegram/SourceFiles/intro/introcode.cpp index 9895aba917..4f64a2c4be 100644 --- a/Telegram/SourceFiles/intro/introcode.cpp +++ b/Telegram/SourceFiles/intro/introcode.cpp @@ -158,6 +158,14 @@ void IntroCode::activate() { code.setFocus(); } +void IntroCode::prepareShow() { + code.setText(QString()); + if (sentRequest) { + MTP::cancel(sentRequest); + sentRequest = 0; + } +} + void IntroCode::deactivate() { callTimer.stop(); hide(); @@ -222,8 +230,7 @@ bool IntroCode::codeSubmitFail(const RPCError &error) { checkRequest.start(1000); sentRequest = MTP::send(MTPaccount_GetPassword(), rpcDone(&IntroCode::gotPassword), rpcFail(&IntroCode::codeSubmitFail)); return true; - } - if (QRegularExpression("^FLOOD_WAIT_(\\d+)$").match(err).hasMatch()) { + } else if (error.type().startsWith(qsl("FLOOD_WAIT_"))) { showError(lang(lng_flood_error)); code.setFocus(); return true; diff --git a/Telegram/SourceFiles/intro/introcode.h b/Telegram/SourceFiles/intro/introcode.h index 67c70b6f80..6e44aa8279 100644 --- a/Telegram/SourceFiles/intro/introcode.h +++ b/Telegram/SourceFiles/intro/introcode.h @@ -52,6 +52,7 @@ public: bool animStep(float64 ms); void activate(); + void prepareShow(); void deactivate(); void onNext(); void onBack(); diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp index 46a81a96a7..8336cfc341 100644 --- a/Telegram/SourceFiles/intro/introphone.cpp +++ b/Telegram/SourceFiles/intro/introphone.cpp @@ -266,8 +266,7 @@ bool IntroPhone::phoneSubmitFail(const RPCError &error) { showError(lang(lng_bad_phone)); enableAll(true); return true; - } - if (QRegularExpression("^FLOOD_WAIT_(\\d+)$").match(err).hasMatch()) { + } else if (error.type().startsWith(qsl("FLOOD_WAIT_"))) { showError(lang(lng_flood_error)); enableAll(true); return true; diff --git a/Telegram/SourceFiles/intro/intropwdcheck.cpp b/Telegram/SourceFiles/intro/intropwdcheck.cpp index 105fd7c620..0b411cf44e 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.cpp +++ b/Telegram/SourceFiles/intro/intropwdcheck.cpp @@ -36,7 +36,9 @@ _hint(parent->getPwdHint()), _pwdField(this, st::inpIntroPassword, lang(lng_signin_password)), _codeField(this, st::inpIntroPassword, lang(lng_signin_code)), _toRecover(this, lang(lng_signin_recover)), -_toPassword(this, lang(lng_signin_try_password)) { +_toPassword(this, lang(lng_signin_try_password)), +_reset(this, lang(lng_signin_reset_account), st::btnRedLink), +sentRequest(0) { setVisible(false); setGeometry(parent->innerRect()); @@ -46,6 +48,7 @@ _toPassword(this, lang(lng_signin_try_password)) { connect(&_toPassword, SIGNAL(clicked()), this, SLOT(onToPassword())); connect(&_pwdField, SIGNAL(changed()), this, SLOT(onInputChange())); connect(&_codeField, SIGNAL(changed()), this, SLOT(onInputChange())); + connect(&_reset, SIGNAL(clicked()), this, SLOT(onReset())); _pwdField.setEchoMode(QLineEdit::Password); @@ -54,7 +57,8 @@ _toPassword(this, lang(lng_signin_try_password)) { } _codeField.hide(); _toPassword.hide(); - _toRecover.setVisible(_hasRecovery); + _toRecover.show(); + _reset.hide(); setMouseTracking(true); } @@ -98,6 +102,7 @@ void IntroPwdCheck::resizeEvent(QResizeEvent *e) { _codeField.move((width() - _codeField.width()) / 2, st::introTextTop + st::introTextSize.height() + st::introCountry.top); _toRecover.move(_next.x() + (_pwdField.width() - _toRecover.width()) / 2, _next.y() + _next.height() + st::introFinishSkip); _toPassword.move(_next.x() + (_pwdField.width() - _toPassword.width()) / 2, _next.y() + _next.height() + st::introFinishSkip); + _reset.move((width() - _reset.width()) / 2, _toRecover.y() + _toRecover.height() + st::introFinishSkip); } textRect = QRect((width() - st::introTextSize.width()) / 2, st::introTextTop, st::introTextSize.width(), st::introTextSize.height()); } @@ -168,6 +173,7 @@ void IntroPwdCheck::onCheckRequest() { } void IntroPwdCheck::pwdSubmitDone(bool recover, const MTPauth_Authorization &result) { + sentRequest = 0; stopCheck(); if (recover) { cSetPasswordRecovered(true); @@ -183,6 +189,7 @@ void IntroPwdCheck::pwdSubmitDone(bool recover, const MTPauth_Authorization &res } bool IntroPwdCheck::pwdSubmitFail(const RPCError &error) { + sentRequest = 0; stopCheck(); _pwdField.setDisabled(false); _codeField.setDisabled(false); @@ -193,10 +200,9 @@ bool IntroPwdCheck::pwdSubmitFail(const RPCError &error) { return true; } else if (err == "PASSWORD_EMPTY") { intro()->onIntroBack(); - } - if (QRegularExpression("^FLOOD_WAIT_(\\d+)$").match(err).hasMatch()) { + } else if (err.startsWith(qsl("FLOOD_WAIT_"))) { showError(lang(lng_flood_error)); - _pwdField.setFocus(); + _pwdField.notaBene(); return true; } if (cDebug()) { // internal server error @@ -209,6 +215,7 @@ bool IntroPwdCheck::pwdSubmitFail(const RPCError &error) { } bool IntroPwdCheck::codeSubmitFail(const RPCError &error) { + sentRequest = 0; stopCheck(); _pwdField.setDisabled(false); _codeField.setDisabled(false); @@ -225,10 +232,9 @@ bool IntroPwdCheck::codeSubmitFail(const RPCError &error) { return true; } else if (err == "CODE_INVALID") { showError(lang(lng_signin_wrong_code)); - _pwdField.notaBene(); + _codeField.notaBene(); return true; - } - if (QRegularExpression("^FLOOD_WAIT_(\\d+)$").match(err).hasMatch()) { + } else if (err.startsWith(qsl("FLOOD_WAIT_"))) { showError(lang(lng_flood_error)); _codeField.notaBene(); return true; @@ -260,34 +266,80 @@ bool IntroPwdCheck::recoverStartFail(const RPCError &error) { } void IntroPwdCheck::onToRecover() { - showError(""); - _toRecover.hide(); - _toPassword.show(); - _pwdField.hide(); - _pwdField.setText(QString()); - _codeField.show(); - _codeField.setFocus(); - if (_emailPattern.isEmpty()) { - MTP::send(MTPauth_RequestPasswordRecovery(), rpcDone(&IntroPwdCheck::recoverStarted), rpcFail(&IntroPwdCheck::recoverStartFail)); + if (_hasRecovery) { + if (sentRequest) { + MTP::cancel(sentRequest); + sentRequest = 0; + } + showError(""); + _toRecover.hide(); + _toPassword.show(); + _pwdField.hide(); + _pwdField.setText(QString()); + _codeField.show(); + _codeField.setFocus(); + if (_emailPattern.isEmpty()) { + MTP::send(MTPauth_RequestPasswordRecovery(), rpcDone(&IntroPwdCheck::recoverStarted), rpcFail(&IntroPwdCheck::recoverStartFail)); + } + update(); + } else { + ConfirmBox *box = new ConfirmBox(lang(lng_signin_no_email_forgot), true); + App::wnd()->showLayer(box); + connect(box, SIGNAL(destroyed(QObject*)), this, SLOT(onToReset())); } - update(); } void IntroPwdCheck::onToPassword() { + ConfirmBox *box = new ConfirmBox(lang(lng_signin_no_email_forgot), true); + App::wnd()->showLayer(box); + connect(box, SIGNAL(destroyed(QObject*)), this, SLOT(onToReset())); +} + +void IntroPwdCheck::onToReset() { + if (sentRequest) { + MTP::cancel(sentRequest); + sentRequest = 0; + } _toRecover.show(); _toPassword.hide(); _pwdField.show(); _codeField.hide(); _codeField.setText(QString()); _pwdField.setFocus(); + _reset.show(); update(); } +void IntroPwdCheck::onReset() { + if (sentRequest) return; + ConfirmBox *box = new ConfirmBox(lang(lng_sigin_sure_reset), lang(lng_sigin_reset), QString(), st::btnRedDone); + connect(box, SIGNAL(confirmed()), this, SLOT(onResetSure())); + App::wnd()->showLayer(box); +} + +void IntroPwdCheck::onResetSure() { + if (sentRequest) return; + sentRequest = MTP::send(MTPaccount_DeleteAccount(MTP_string("Forgot password")), rpcDone(&IntroPwdCheck::deleteDone), rpcFail(&IntroPwdCheck::deleteFail)); +} + +bool IntroPwdCheck::deleteFail(const RPCError &error) { + if (mtpIsFlood(error)) return false; + sentRequest = 0; + showError(lang(lng_server_error)); + return true; +} + +void IntroPwdCheck::deleteDone(const MTPBool &v) { + App::wnd()->hideLayer(); + intro()->onIntroNext(); +} + void IntroPwdCheck::onInputChange() { showError(""); } void IntroPwdCheck::onSubmitPwd(bool force) { + if (sentRequest) return; if (_pwdField.isHidden()) { if (!force && !_codeField.isEnabled()) return; QString code = _codeField.text().trimmed(); diff --git a/Telegram/SourceFiles/intro/intropwdcheck.h b/Telegram/SourceFiles/intro/intropwdcheck.h index b10467116e..175d94957d 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.h +++ b/Telegram/SourceFiles/intro/intropwdcheck.h @@ -53,12 +53,18 @@ public slots: void onToPassword(); void onInputChange(); void onCheckRequest(); + void onToReset(); + void onReset(); + void onResetSure(); private: void showError(const QString &err); void stopCheck(); + void deleteDone(const MTPBool &result); + bool deleteFail(const RPCError &error); + QString error; anim::fvalue errorAlpha; @@ -71,7 +77,7 @@ private: QString _hint, _emailPattern; FlatInput _pwdField, _codeField; - LinkButton _toRecover, _toPassword; + LinkButton _toRecover, _toPassword, _reset; mtpRequestId sentRequest; Text _hintText; diff --git a/Telegram/SourceFiles/intro/introsignup.cpp b/Telegram/SourceFiles/intro/introsignup.cpp index fa96cb36df..bfe4ad2f99 100644 --- a/Telegram/SourceFiles/intro/introsignup.cpp +++ b/Telegram/SourceFiles/intro/introsignup.cpp @@ -239,8 +239,7 @@ bool IntroSignup::nameSubmitFail(const RPCError &error) { showError(lang(lng_bad_name)); last.setFocus(); return true; - } - if (QRegularExpression("^FLOOD_WAIT_(\\d+)$").match(err).hasMatch()) { + } else if (error.type().startsWith(qsl("FLOOD_WAIT_"))) { showError(lang(lng_flood_error)); last.setFocus(); return true; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index a2acceb3fc..cdaf5155ad 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -483,31 +483,37 @@ void MainWidget::cancelForwarding() { } void MainWidget::finishForwarding(History *hist) { - if (_toForward.isEmpty() || !hist) return; - App::main()->readServerHistory(hist, false); - if (_toForward.size() < 2) { - uint64 randomId = MTP::nonce(); - MsgId newId = clientMsgId(); - hist->addToBackForwarded(newId, static_cast(_toForward.cbegin().value())); - App::historyRegRandom(randomId, newId); - hist->sendRequestId = MTP::send(MTPmessages_ForwardMessage(hist->peer->input, MTP_int(_toForward.cbegin().key()), MTP_long(randomId)), rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); - } else { - QVector ids; - QVector randomIds; - ids.reserve(_toForward.size()); - randomIds.reserve(_toForward.size()); - for (SelectedItemSet::const_iterator i = _toForward.cbegin(), e = _toForward.cend(); i != e; ++i) { + if (!hist) return; + if (!_toForward.isEmpty()) { + App::main()->readServerHistory(hist, false); + if (_toForward.size() < 2) { uint64 randomId = MTP::nonce(); - //MsgId newId = clientMsgId(); - //hist->addToBackForwarded(newId, static_cast(i.value())); - //App::historyRegRandom(randomId, newId); - ids.push_back(MTP_int(i.key())); - randomIds.push_back(MTP_long(randomId)); + MsgId newId = clientMsgId(); + hist->addToBackForwarded(newId, static_cast(_toForward.cbegin().value())); + App::historyRegRandom(randomId, newId); + hist->sendRequestId = MTP::send(MTPmessages_ForwardMessage(hist->peer->input, MTP_int(_toForward.cbegin().key()), MTP_long(randomId)), rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); + } else { + QVector ids; + QVector randomIds; + ids.reserve(_toForward.size()); + randomIds.reserve(_toForward.size()); + for (SelectedItemSet::const_iterator i = _toForward.cbegin(), e = _toForward.cend(); i != e; ++i) { + uint64 randomId = MTP::nonce(); + //MsgId newId = clientMsgId(); + //hist->addToBackForwarded(newId, static_cast(i.value())); + //App::historyRegRandom(randomId, newId); + ids.push_back(MTP_int(i.key())); + randomIds.push_back(MTP_long(randomId)); + } + hist->sendRequestId = MTP::send(MTPmessages_ForwardMessages(hist->peer->input, MTP_vector(ids), MTP_vector(randomIds)), rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); } - hist->sendRequestId = MTP::send(MTPmessages_ForwardMessages(hist->peer->input, MTP_vector(ids), MTP_vector(randomIds)), rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); + if (history.peer() == hist->peer) history.peerMessagesUpdated(); + cancelForwarding(); } - if (history.peer() == hist->peer) history.peerMessagesUpdated(); - cancelForwarding(); + + historyToDown(hist); + dialogsToUp(); + history.peerMessagesUpdated(hist->peer->id); } void MainWidget::onShareContact(const PeerId &peer, UserData *contact) { @@ -556,9 +562,14 @@ void MainWidget::noHider(HistoryHider *destroyed) { } void MainWidget::hiderLayer(HistoryHider *h) { - if (App::passcoded()) return; + if (App::passcoded()) { + delete h; + return; + } hider = h; + connect(hider, SIGNAL(forwarded()), &dialogs, SLOT(onCancelSearch())); + dialogsToUp(); if (cWideMode()) { hider->show(); resizeEvent(0); @@ -604,8 +615,8 @@ void MainWidget::shareContactLayer(UserData *contact) { hiderLayer(new HistoryHider(this, contact)); } -bool MainWidget::selectingPeer() { - return !!hider; +bool MainWidget::selectingPeer(bool withConfirm) { + return hider ? (withConfirm ? hider->withConfirm() : true) : false; } void MainWidget::offerPeer(PeerId peer) { @@ -636,8 +647,10 @@ void MainWidget::dialogsActivate() { dialogs.activate(); } -bool MainWidget::leaveChatFailed(PeerData *peer, const RPCError &e) { - if (e.type() == "CHAT_ID_INVALID") { // left this chat already +bool MainWidget::leaveChatFailed(PeerData *peer, const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + + if (error.type() == "CHAT_ID_INVALID") { // left this chat already if ((profile && profile->peer() == peer) || (overview && overview->peer() == peer) || _stack.contains(peer) || history.peer() == peer) { showPeer(0, 0, false, true); } @@ -695,7 +708,7 @@ void MainWidget::clearHistory(PeerData *peer) { if (!peer->chat && peer->asUser()->contact <= 0) { dialogs.removePeer(peer->asUser()); } - dialogs.dialogsToUp(); + dialogsToUp(); dialogs.update(); App::history(peer->id)->clear(); MTP::send(MTPmessages_DeleteHistory(peer->input, MTP_int(0)), rpcDone(&MainWidget::deleteHistoryPart, peer)); @@ -713,10 +726,12 @@ void MainWidget::addParticipants(ChatData *chat, const QVector &users showPeer(chat->id, 0, false); } -bool MainWidget::addParticipantFail(ChatData *chat, const RPCError &e) { +bool MainWidget::addParticipantFail(ChatData *chat, const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + ConfirmBox *box = new ConfirmBox(lang(lng_failed_add_participant), true); App::wnd()->showLayer(box); - if (e.type() == "USER_LEFT_CHAT") { // trying to return banned user to his group + if (error.type() == "USER_LEFT_CHAT") { // trying to return banned user to his group } return false; } @@ -727,8 +742,10 @@ void MainWidget::kickParticipant(ChatData *chat, UserData *user) { showPeer(chat->id, 0, false); } -bool MainWidget::kickParticipantFail(ChatData *chat, const RPCError &e) { - e.type(); +bool MainWidget::kickParticipantFail(ChatData *chat, const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + + error.type(); return false; } @@ -764,13 +781,15 @@ void MainWidget::checkedHistory(PeerData *peer, const MTPmessages_Messages &resu } } -bool MainWidget::sendPhotoFailed(uint64 randomId, const RPCError &e) { - if (e.type() == qsl("PHOTO_INVALID_DIMENSIONS")) { +bool MainWidget::sendPhotoFailed(uint64 randomId, const RPCError &error) { + if (mtpIsFlood(error)) return false; + + if (error.type() == qsl("PHOTO_INVALID_DIMENSIONS")) { if (_resendImgRandomIds.isEmpty()) { ConfirmBox *box = new ConfirmBox(lang(lng_bad_image_for_photo)); connect(box, SIGNAL(confirmed()), this, SLOT(onResendAsDocument())); connect(box, SIGNAL(cancelled()), this, SLOT(onCancelResend())); - connect(box, SIGNAL(destroyed()), this, SLOT(onCancelResend())); + connect(box, SIGNAL(destroyed(QObject*)), this, SLOT(onCancelResend())); App::wnd()->showLayer(box); } _resendImgRandomIds.push_back(randomId); @@ -886,11 +905,6 @@ void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId repl } finishForwarding(hist); - - historyToDown(hist); - if (history.peer() == hist->peer) { - history.peerMessagesUpdated(); - } } void MainWidget::sendMessage(History *hist, const QString &text, MsgId replyTo) { @@ -1088,16 +1102,18 @@ void MainWidget::itemReplaced(HistoryItem *oldItem, HistoryItem *newItem) { } } -void MainWidget::itemResized(HistoryItem *row) { +void MainWidget::itemResized(HistoryItem *row, bool scrollToIt) { if (!row || (history.peer() == row->history()->peer && !row->detached())) { - history.itemResized(row); + history.itemResized(row, scrollToIt); } if (overview) { - overview->itemResized(row); + overview->itemResized(row, scrollToIt); } } bool MainWidget::overviewFailed(PeerData *peer, const RPCError &error, mtpRequestId req) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + MediaOverviewType type = OverviewCount; for (int32 i = 0; i < OverviewCount; ++i) { OverviewsPreload::iterator j = _overviewPreload[i].find(peer); @@ -1463,6 +1479,8 @@ void MainWidget::serviceHistoryDone(const MTPmessages_Messages &msgs) { } bool MainWidget::serviceHistoryFail(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + App::wnd()->showDelayedServiceMsgs(); return false; } @@ -1860,6 +1878,13 @@ void MainWidget::sentDataReceived(uint64 randomId, const MTPmessages_SentMessage return; } } + + if (d.vmedia.type() != mtpc_messageMediaEmpty) { + HistoryItem *item = App::histItemById(d.vid.v); + if (item) { + item->setMedia(d.vmedia); + } + } } break; case mtpc_messages_sentMessageLink: { @@ -1874,6 +1899,13 @@ void MainWidget::sentDataReceived(uint64 randomId, const MTPmessages_SentMessage } } App::feedUserLinks(d.vlinks); + + if (d.vmedia.type() != mtpc_messageMediaEmpty) { + HistoryItem *item = App::histItemById(d.vid.v); + if (item) { + item->setMedia(d.vmedia); + } + } } break; }; } @@ -2293,8 +2325,10 @@ void MainWidget::feedDifference(const MTPVector &users, const MTPVector history.peerMessagesUpdated(); } -bool MainWidget::failDifference(const RPCError &e) { - LOG(("RPC Error: %1 %2: %3").arg(e.code()).arg(e.type()).arg(e.description())); +bool MainWidget::failDifference(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + + LOG(("RPC Error: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description())); _failDifferenceTimer.start(_failDifferenceTimeout * 1000); if (_failDifferenceTimeout < 64) _failDifferenceTimeout *= 2; return true; @@ -2385,6 +2419,8 @@ void MainWidget::usernameResolveDone(bool toProfile, const MTPUser &user) { } bool MainWidget::usernameResolveFail(QString name, const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + if (error.code() == 400) { App::wnd()->showLayer(new ConfirmBox(lng_username_not_found(lt_user, name), true)); } @@ -2479,7 +2515,9 @@ void MainWidget::gotNotifySetting(MTPInputNotifyPeer peer, const MTPPeerNotifySe App::wnd()->notifySettingGot(); } -bool MainWidget::failNotifySetting(MTPInputNotifyPeer peer) { +bool MainWidget::failNotifySetting(MTPInputNotifyPeer peer, const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + gotNotifySetting(peer, MTP_peerNotifySettingsEmpty()); return true; } @@ -2852,7 +2890,15 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { case mtpc_updateWebPage: { const MTPDupdateWebPage &d(update.c_updateWebPage()); - // + WebPageData *page = App::feedWebPage(d.vwebpage); + const WebPageItems &items(App::webPageItems()); + WebPageItems::const_iterator i = items.constFind(page); + if (i != items.cend()) { + for (HistoryItemsMap::const_iterator j = i.value().cbegin(), e = i.value().cend(); j != e; ++j) { + j.key()->initDimensions(); + itemResized(j.key()); + } + } } break; case mtpc_updateDeleteMessages: { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 57dfcd6d14..7e6e7fcbee 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -191,7 +191,7 @@ public: bool started(); void applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNotifySettings &settings, History *history = 0); void gotNotifySetting(MTPInputNotifyPeer peer, const MTPPeerNotifySettings &settings); - bool failNotifySetting(MTPInputNotifyPeer peer); + bool failNotifySetting(MTPInputNotifyPeer peer, const RPCError &error); void updateNotifySetting(PeerData *peer, bool enabled); @@ -251,7 +251,7 @@ public: void onForward(const PeerId &peer, bool forwardSelected); void onShareContact(const PeerId &peer, UserData *contact); void onSendPaths(const PeerId &peer); - bool selectingPeer(); + bool selectingPeer(bool withConfirm = false); void offerPeer(PeerId peer); void focusPeerSelect(); void dialogsActivate(); @@ -297,7 +297,7 @@ public: void changingMsgId(HistoryItem *row, MsgId newId); void itemRemoved(HistoryItem *item); void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem); - void itemResized(HistoryItem *row); + void itemResized(HistoryItem *row, bool scrollToIt = false); void loadMediaBack(PeerData *peer, MediaOverviewType type, bool many = false); void peerUsernameChanged(PeerData *peer); diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 8128abe636..0b554a8cfd 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -517,7 +517,19 @@ void MediaView::showPhoto(PhotoData *photo) { _x = (_avail.width() - _w) / 2; _y = st::medviewPolaroid.top() + (_avail.height() - st::medviewPolaroid.top() - st::medviewPolaroid.bottom() - st::medviewBottomBar - _h) / 2; _width = _w; - _from = App::user(_photo->user); + if (_photo->user == WebPageUserId && _msgid) { + if (HistoryItem *item = App::histItemById(_msgid)) { + if (dynamic_cast(item)) { + _from = static_cast(item)->fromForwarded(); + } else { + _from = item->from(); + } + } else { + _from = App::user(_photo->user); + } + } else { + _from = App::user(_photo->user); + } updateControls(); _photo->full->load(); if (isHidden()) { diff --git a/Telegram/SourceFiles/mtproto/mtp.cpp b/Telegram/SourceFiles/mtproto/mtp.cpp index 14a0b764fa..ce7867553e 100644 --- a/Telegram/SourceFiles/mtproto/mtp.cpp +++ b/Telegram/SourceFiles/mtproto/mtp.cpp @@ -112,6 +112,8 @@ namespace { } bool importFail(const RPCError &error, mtpRequestId req) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + if (globalHandler.onFail && MTP::authedId()) (*globalHandler.onFail)(req, error); // auth import failed return true; } @@ -131,6 +133,8 @@ namespace { } bool exportFail(const RPCError &error, mtpRequestId req) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + AuthExportRequests::const_iterator i = authExportRequests.constFind(req); if (i != authExportRequests.cend()) { authWaiters[i.value()].clear(); @@ -535,6 +539,10 @@ namespace _mtp_internal { } bool rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err) { // return true if need to clean request data + if (err.type().startsWith(qsl("FLOOD_WAIT_"))) { + if (onFail && (*onFail)(requestId, err)) return true; + } + if (onErrorDefault(requestId, err)) { return false; } @@ -633,8 +641,8 @@ namespace MTP { void setdc(int32 dc, bool fromZeroOnly) { if (!dc || !_started) return; mtpSetDC(dc, fromZeroOnly); - if (dc != mainSession->getDC()) { - mainSession = _mtp_internal::getSession(dc); + if (maindc() != mainSession->getDC()) { + mainSession = _mtp_internal::getSession(maindc()); } Local::writeMtpData(); } diff --git a/Telegram/SourceFiles/mtproto/mtpDC.cpp b/Telegram/SourceFiles/mtproto/mtpDC.cpp index a90864d60c..5d9635a7b7 100644 --- a/Telegram/SourceFiles/mtproto/mtpDC.cpp +++ b/Telegram/SourceFiles/mtproto/mtpDC.cpp @@ -142,7 +142,9 @@ namespace { mtpConfigLoader()->done(); } - bool configFailed(const RPCError &err) { + bool configFailed(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + loadingConfig = false; LOG(("MTP Error: failed to get config!")); return false; diff --git a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp index 07634f133c..4b5d07e4ef 100644 --- a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp +++ b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp @@ -272,6 +272,8 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe } bool mtpFileLoader::partFailed(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + finishFail(); return true; } diff --git a/Telegram/SourceFiles/mtproto/mtpRPC.h b/Telegram/SourceFiles/mtproto/mtpRPC.h index b2a4035376..e214ff51a0 100644 --- a/Telegram/SourceFiles/mtproto/mtpRPC.h +++ b/Telegram/SourceFiles/mtproto/mtpRPC.h @@ -61,6 +61,10 @@ private: QString _type, _description; }; +inline bool mtpIsFlood(const RPCError &error) { + return error.type().startsWith(qsl("FLOOD_WAIT_")); +} + class RPCAbstractDoneHandler { // abstract done public: virtual void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) const = 0; diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.cpp b/Telegram/SourceFiles/mtproto/mtpScheme.cpp index 22bcb093b0..0fe6a5050c 100644 --- a/Telegram/SourceFiles/mtproto/mtpScheme.cpp +++ b/Telegram/SourceFiles/mtproto/mtpScheme.cpp @@ -4013,11 +4013,21 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" display_url: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" title: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" description: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 4: to.add(" photo: "); ++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_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" url: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" display_url: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" type: "); ++stages.back(); if (flag & MTPDwebPage::flag_type) { types.push_back(mtpc_string); 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(" site_name: "); ++stages.back(); if (flag & MTPDwebPage::flag_site_name) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; + case 6: to.add(" title: "); ++stages.back(); if (flag & MTPDwebPage::flag_title) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; + case 7: to.add(" description: "); ++stages.back(); if (flag & MTPDwebPage::flag_description) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 8: to.add(" photo: "); ++stages.back(); if (flag & MTPDwebPage::flag_photo) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 4 IN FIELD flags ]"); } break; + case 9: to.add(" embed_url: "); ++stages.back(); if (flag & MTPDwebPage::flag_embed_url) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 5 IN FIELD flags ]"); } break; + case 10: to.add(" embed_type: "); ++stages.back(); if (flag & MTPDwebPage::flag_embed_type) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 5 IN FIELD flags ]"); } break; + case 11: to.add(" embed_width: "); ++stages.back(); if (flag & MTPDwebPage::flag_embed_width) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; + case 12: to.add(" embed_height: "); ++stages.back(); if (flag & MTPDwebPage::flag_embed_height) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; + case 13: to.add(" duration: "); ++stages.back(); if (flag & MTPDwebPage::flag_duration) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 7 IN FIELD flags ]"); } break; + case 14: to.add(" author: "); ++stages.back(); if (flag & MTPDwebPage::flag_author) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 8 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } break; diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.h b/Telegram/SourceFiles/mtproto/mtpScheme.h index 3f3fffed87..aa5558f88d 100644 --- a/Telegram/SourceFiles/mtproto/mtpScheme.h +++ b/Telegram/SourceFiles/mtproto/mtpScheme.h @@ -361,7 +361,7 @@ enum { mtpc_updateWebPage = 0x2cc36971, mtpc_webPageEmpty = 0xeb1477e8, mtpc_webPagePending = 0xc586da1c, - mtpc_webPage = 0x39c1cef9, + mtpc_webPage = 0xa31ea0b5, mtpc_messageMediaWebPage = 0xa32dd600, mtpc_authorization = 0x7bf2e6f6, mtpc_account_authorizations = 0x1250abde, @@ -7290,7 +7290,7 @@ private: friend MTPwebPage MTP_webPageEmpty(const MTPlong &_id); friend MTPwebPage MTP_webPagePending(const MTPlong &_id, MTPint _date); - friend MTPwebPage MTP_webPage(const MTPlong &_id, const MTPstring &_display_url, const MTPstring &_title, const MTPstring &_description, const MTPPhoto &_photo); + friend MTPwebPage MTP_webPage(MTPint _flags, const MTPlong &_id, const MTPstring &_url, const MTPstring &_display_url, const MTPstring &_type, const MTPstring &_site_name, const MTPstring &_title, const MTPstring &_description, const MTPPhoto &_photo, const MTPstring &_embed_url, const MTPstring &_embed_type, MTPint _embed_width, MTPint _embed_height, MTPint _duration, const MTPstring &_author); mtpTypeId _type; }; @@ -8511,13 +8511,13 @@ public: MTPMessageMedia vmedia; enum { - flag_reply_to_msg_id = (1 << 3), flag_fwd_date = (1 << 2), + flag_reply_to_msg_id = (1 << 3), flag_fwd_from_id = (1 << 2), }; - bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } bool has_fwd_date() const { return vflags.v & flag_fwd_date; } + bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } bool has_fwd_from_id() const { return vflags.v & flag_fwd_from_id; } }; @@ -9597,13 +9597,13 @@ public: MTPint vreply_to_msg_id; enum { - flag_reply_to_msg_id = (1 << 3), flag_fwd_date = (1 << 2), + flag_reply_to_msg_id = (1 << 3), flag_fwd_from_id = (1 << 2), }; - bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } bool has_fwd_date() const { return vflags.v & flag_fwd_date; } + bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } bool has_fwd_from_id() const { return vflags.v & flag_fwd_from_id; } }; @@ -9627,13 +9627,13 @@ public: MTPint vreply_to_msg_id; enum { - flag_reply_to_msg_id = (1 << 3), flag_fwd_date = (1 << 2), + flag_reply_to_msg_id = (1 << 3), flag_fwd_from_id = (1 << 2), }; - bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } bool has_fwd_date() const { return vflags.v & flag_fwd_date; } + bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } bool has_fwd_from_id() const { return vflags.v & flag_fwd_from_id; } }; @@ -10419,14 +10419,50 @@ class MTPDwebPage : public mtpDataImpl { public: MTPDwebPage() { } - MTPDwebPage(const MTPlong &_id, const MTPstring &_display_url, const MTPstring &_title, const MTPstring &_description, const MTPPhoto &_photo) : vid(_id), vdisplay_url(_display_url), vtitle(_title), vdescription(_description), vphoto(_photo) { + MTPDwebPage(MTPint _flags, const MTPlong &_id, const MTPstring &_url, const MTPstring &_display_url, const MTPstring &_type, const MTPstring &_site_name, const MTPstring &_title, const MTPstring &_description, const MTPPhoto &_photo, const MTPstring &_embed_url, const MTPstring &_embed_type, MTPint _embed_width, MTPint _embed_height, MTPint _duration, const MTPstring &_author) : vflags(_flags), vid(_id), vurl(_url), vdisplay_url(_display_url), vtype(_type), vsite_name(_site_name), vtitle(_title), vdescription(_description), vphoto(_photo), vembed_url(_embed_url), vembed_type(_embed_type), vembed_width(_embed_width), vembed_height(_embed_height), vduration(_duration), vauthor(_author) { } + MTPint vflags; MTPlong vid; + MTPstring vurl; MTPstring vdisplay_url; + MTPstring vtype; + MTPstring vsite_name; MTPstring vtitle; MTPstring vdescription; MTPPhoto vphoto; + MTPstring vembed_url; + MTPstring vembed_type; + MTPint vembed_width; + MTPint vembed_height; + MTPint vduration; + MTPstring vauthor; + + enum { + flag_embed_height = (1 << 6), + flag_embed_type = (1 << 5), + flag_duration = (1 << 7), + flag_photo = (1 << 4), + flag_embed_url = (1 << 5), + flag_author = (1 << 8), + flag_description = (1 << 3), + flag_type = (1 << 0), + flag_title = (1 << 2), + flag_embed_width = (1 << 6), + flag_site_name = (1 << 1), + }; + + bool has_embed_height() const { return vflags.v & flag_embed_height; } + bool has_embed_type() const { return vflags.v & flag_embed_type; } + bool has_duration() const { return vflags.v & flag_duration; } + bool has_photo() const { return vflags.v & flag_photo; } + bool has_embed_url() const { return vflags.v & flag_embed_url; } + bool has_author() const { return vflags.v & flag_author; } + bool has_description() const { return vflags.v & flag_description; } + bool has_type() const { return vflags.v & flag_type; } + bool has_title() const { return vflags.v & flag_title; } + bool has_embed_width() const { return vflags.v & flag_embed_width; } + bool has_site_name() const { return vflags.v & flag_site_name; } }; class MTPDauthorization : public mtpDataImpl { @@ -10512,14 +10548,14 @@ public: enum { flag_new_salt = (1 << 0), flag_new_password_hash = (1 << 0), - flag_email = (1 << 1), flag_hint = (1 << 0), + flag_email = (1 << 1), }; bool has_new_salt() const { return vflags.v & flag_new_salt; } bool has_new_password_hash() const { return vflags.v & flag_new_password_hash; } - bool has_email() const { return vflags.v & flag_email; } bool has_hint() const { return vflags.v & flag_hint; } + bool has_email() const { return vflags.v & flag_email; } }; class MTPDauth_passwordRecovery : public mtpDataImpl { @@ -23956,7 +23992,7 @@ inline uint32 MTPwebPage::innerLength() const { } case mtpc_webPage: { const MTPDwebPage &v(c_webPage()); - return v.vid.innerLength() + v.vdisplay_url.innerLength() + v.vtitle.innerLength() + v.vdescription.innerLength() + v.vphoto.innerLength(); + return v.vflags.innerLength() + v.vid.innerLength() + v.vurl.innerLength() + v.vdisplay_url.innerLength() + (v.has_type() ? v.vtype.innerLength() : 0) + (v.has_site_name() ? v.vsite_name.innerLength() : 0) + (v.has_title() ? v.vtitle.innerLength() : 0) + (v.has_description() ? v.vdescription.innerLength() : 0) + (v.has_photo() ? v.vphoto.innerLength() : 0) + (v.has_embed_url() ? v.vembed_url.innerLength() : 0) + (v.has_embed_type() ? v.vembed_type.innerLength() : 0) + (v.has_embed_width() ? v.vembed_width.innerLength() : 0) + (v.has_embed_height() ? v.vembed_height.innerLength() : 0) + (v.has_duration() ? v.vduration.innerLength() : 0) + (v.has_author() ? v.vauthor.innerLength() : 0); } } return 0; @@ -23982,11 +24018,21 @@ inline void MTPwebPage::read(const mtpPrime *&from, const mtpPrime *end, mtpType case mtpc_webPage: _type = cons; { if (!data) setData(new MTPDwebPage()); MTPDwebPage &v(_webPage()); + v.vflags.read(from, end); v.vid.read(from, end); + v.vurl.read(from, end); v.vdisplay_url.read(from, end); - v.vtitle.read(from, end); - v.vdescription.read(from, end); - v.vphoto.read(from, end); + if (v.has_type()) { v.vtype.read(from, end); } else { v.vtype = MTPstring(); } + if (v.has_site_name()) { v.vsite_name.read(from, end); } else { v.vsite_name = MTPstring(); } + if (v.has_title()) { v.vtitle.read(from, end); } else { v.vtitle = MTPstring(); } + if (v.has_description()) { v.vdescription.read(from, end); } else { v.vdescription = MTPstring(); } + if (v.has_photo()) { v.vphoto.read(from, end); } else { v.vphoto = MTPPhoto(); } + if (v.has_embed_url()) { v.vembed_url.read(from, end); } else { v.vembed_url = MTPstring(); } + if (v.has_embed_type()) { v.vembed_type.read(from, end); } else { v.vembed_type = MTPstring(); } + if (v.has_embed_width()) { v.vembed_width.read(from, end); } else { v.vembed_width = MTPint(); } + if (v.has_embed_height()) { v.vembed_height.read(from, end); } else { v.vembed_height = MTPint(); } + if (v.has_duration()) { v.vduration.read(from, end); } else { v.vduration = MTPint(); } + if (v.has_author()) { v.vauthor.read(from, end); } else { v.vauthor = MTPstring(); } } break; default: throw mtpErrorUnexpected(cons, "MTPwebPage"); } @@ -24004,11 +24050,21 @@ inline void MTPwebPage::write(mtpBuffer &to) const { } break; case mtpc_webPage: { const MTPDwebPage &v(c_webPage()); + v.vflags.write(to); v.vid.write(to); + v.vurl.write(to); v.vdisplay_url.write(to); - v.vtitle.write(to); - v.vdescription.write(to); - v.vphoto.write(to); + if (v.has_type()) v.vtype.write(to); + if (v.has_site_name()) v.vsite_name.write(to); + if (v.has_title()) v.vtitle.write(to); + if (v.has_description()) v.vdescription.write(to); + if (v.has_photo()) v.vphoto.write(to); + if (v.has_embed_url()) v.vembed_url.write(to); + if (v.has_embed_type()) v.vembed_type.write(to); + if (v.has_embed_width()) v.vembed_width.write(to); + if (v.has_embed_height()) v.vembed_height.write(to); + if (v.has_duration()) v.vduration.write(to); + if (v.has_author()) v.vauthor.write(to); } break; } } @@ -24032,8 +24088,8 @@ inline MTPwebPage MTP_webPageEmpty(const MTPlong &_id) { inline MTPwebPage MTP_webPagePending(const MTPlong &_id, MTPint _date) { return MTPwebPage(new MTPDwebPagePending(_id, _date)); } -inline MTPwebPage MTP_webPage(const MTPlong &_id, const MTPstring &_display_url, const MTPstring &_title, const MTPstring &_description, const MTPPhoto &_photo) { - return MTPwebPage(new MTPDwebPage(_id, _display_url, _title, _description, _photo)); +inline MTPwebPage MTP_webPage(MTPint _flags, const MTPlong &_id, const MTPstring &_url, const MTPstring &_display_url, const MTPstring &_type, const MTPstring &_site_name, const MTPstring &_title, const MTPstring &_description, const MTPPhoto &_photo, const MTPstring &_embed_url, const MTPstring &_embed_type, MTPint _embed_width, MTPint _embed_height, MTPint _duration, const MTPstring &_author) { + return MTPwebPage(new MTPDwebPage(_flags, _id, _url, _display_url, _type, _site_name, _title, _description, _photo, _embed_url, _embed_type, _embed_width, _embed_height, _duration, _author)); } inline MTPauthorization::MTPauthorization() : mtpDataOwner(new MTPDauthorization()) { diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index 0fd51e9cec..1b0c0c8634 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -556,7 +556,7 @@ updateWebPage#2cc36971 webpage:WebPage = Update; webPageEmpty#eb1477e8 id:long = WebPage; webPagePending#c586da1c id:long date:int = WebPage; -webPage#39c1cef9 id:long display_url:string title:string description:string photo:Photo = WebPage; +webPage#a31ea0b5 flags:# id:long url:string display_url:string type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string = WebPage; messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 12985f481a..c78bfc3292 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -1441,7 +1441,7 @@ void OverviewInner::itemRemoved(HistoryItem *item) { parentWidget()->update(); } -void OverviewInner::itemResized(HistoryItem *item) { +void OverviewInner::itemResized(HistoryItem *item, bool scrollToIt) { if (_type != OverviewPhotos) { HistoryMedia *media = item ? item->getMedia(true) : 0; if (!media) return; @@ -1462,11 +1462,13 @@ void OverviewInner::itemResized(HistoryItem *item) { _height = _items[l - 1].y; _addToY = (_height < _minHeight) ? (_minHeight - _height) : 0; resize(width(), _minHeight > _height ? _minHeight : _height); - if (_addToY + _height - from > _scroll->scrollTop() + _scroll->height()) { - _scroll->scrollToY(_addToY + _height - from - _scroll->height()); - } - if (_addToY + _height - _items[i].y < _scroll->scrollTop()) { - _scroll->scrollToY(_addToY + _height - _items[i].y); + if (scrollToIt) { + if (_addToY + _height - from > _scroll->scrollTop() + _scroll->height()) { + _scroll->scrollToY(_addToY + _height - from - _scroll->height()); + } + if (_addToY + _height - _items[i].y < _scroll->scrollTop()) { + _scroll->scrollToY(_addToY + _height - _items[i].y); + } } parentWidget()->update(); } @@ -1780,9 +1782,9 @@ void OverviewWidget::itemRemoved(HistoryItem *row) { } } -void OverviewWidget::itemResized(HistoryItem *row) { +void OverviewWidget::itemResized(HistoryItem *row, bool scrollToIt) { if (!row || row->history()->peer == peer()) { - _inner.itemResized(row); + _inner.itemResized(row, scrollToIt); } } diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index 3ffb59b659..963844bbde 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -61,7 +61,7 @@ public: void changingMsgId(HistoryItem *row, MsgId newId); void msgUpdated(const HistoryItem *msg); void itemRemoved(HistoryItem *item); - void itemResized(HistoryItem *item); + void itemResized(HistoryItem *item, bool scrollToIt); void getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const; void clearSelectedItems(bool onlyTextSelection = false); @@ -214,7 +214,7 @@ public: void changingMsgId(HistoryItem *row, MsgId newId); void msgUpdated(PeerId peer, const HistoryItem *msg); void itemRemoved(HistoryItem *item); - void itemResized(HistoryItem *row); + void itemResized(HistoryItem *row, bool scrollToIt); QPoint clampMousePosition(QPoint point); diff --git a/Telegram/SourceFiles/passcodewidget.cpp b/Telegram/SourceFiles/passcodewidget.cpp index 7c639528c6..dbb2f19e2c 100644 --- a/Telegram/SourceFiles/passcodewidget.cpp +++ b/Telegram/SourceFiles/passcodewidget.cpp @@ -36,9 +36,6 @@ _logout(this, lang(lng_passcode_logout)) { _passcode.setEchoMode(QLineEdit::Password); connect(&_submit, SIGNAL(clicked()), this, SLOT(onSubmit())); - _errorTimer.setSingleShot(true); - connect(&_errorTimer, SIGNAL(timeout()), this, SLOT(onError())); - connect(&_passcode, SIGNAL(changed()), this, SLOT(onChanged())); connect(&_passcode, SIGNAL(accepted()), this, SLOT(onSubmit())); @@ -58,18 +55,27 @@ void PasscodeWidget::onSubmit() { _passcode.notaBene(); return; } + if (!passcodeCanTry()) { + _error = lang(lng_flood_error); + _passcode.setFocus(); + _passcode.notaBene(); + update(); + return; + } if (App::main()) { if (Local::checkPasscode(_passcode.text().toUtf8())) { + cSetPasscodeBadTries(0); App::wnd()->clearPasscode(); } else { - _error = QString(); - _passcode.setDisabled(true); - _errorTimer.start(WrongPasscodeTimeout); + cSetPasscodeBadTries(cPasscodeBadTries() + 1); + cSetPasscodeLastTry(getms(true)); + onError(); return; } } else { if (Local::readMap(_passcode.text().toUtf8()) != Local::ReadMapPassNeeded) { + cSetPasscodeBadTries(0); App::app()->checkMapVersion(); MTP::start(); @@ -79,10 +85,9 @@ void PasscodeWidget::onSubmit() { App::wnd()->setupIntro(true); } } else { - _error = QString(); - _passcode.setDisabled(true); - _errorTimer.start(WrongPasscodeTimeout); - update(); + cSetPasscodeBadTries(cPasscodeBadTries() + 1); + cSetPasscodeLastTry(getms(true)); + onError(); return; } } @@ -90,7 +95,6 @@ void PasscodeWidget::onSubmit() { void PasscodeWidget::onError() { _error = lang(lng_passcode_wrong); - _passcode.setDisabled(false); _passcode.selectAll(); _passcode.setFocus(); _passcode.notaBene(); diff --git a/Telegram/SourceFiles/passcodewidget.h b/Telegram/SourceFiles/passcodewidget.h index 9268ce4dd0..3dd795c2da 100644 --- a/Telegram/SourceFiles/passcodewidget.h +++ b/Telegram/SourceFiles/passcodewidget.h @@ -57,6 +57,5 @@ private: FlatButton _submit; LinkButton _logout; QString _error; - QTimer _errorTimer; }; diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index a1015086a3..a2821b219d 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -95,6 +95,8 @@ RecentStickerPack gRecentStickers; RecentHashtagPack gRecentWriteHashtags, gRecentSearchHashtags; bool gPasswordRecovered = false; +int32 gPasscodeBadTries = 0; +uint64 gPasscodeLastTry = 0; int32 gLang = -2; // auto QString gLangFile; diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index fa0be104a8..0cf7eb7d05 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -183,6 +183,22 @@ DeclareSetting(RecentHashtagPack, RecentSearchHashtags); DeclareSetting(bool, PasswordRecovered); +DeclareSetting(int32, PasscodeBadTries); +DeclareSetting(uint64, PasscodeLastTry); + +inline bool passcodeCanTry() { + if (cPasscodeBadTries() < 3) return true; + uint64 dt = getms(true) - cPasscodeLastTry(); + switch (cPasscodeBadTries()) { + case 3: return dt >= 5000; + case 4: return dt >= 10000; + case 5: return dt >= 15000; + case 6: return dt >= 20000; + case 7: return dt >= 25000; + } + return dt >= 30000; +} + inline void incrementRecentHashtag(RecentHashtagPack &recent, const QString &tag) { RecentHashtagPack::iterator i = recent.begin(), e = recent.end(); for (; i != e; ++i) { diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp index a6c0629c6a..e598810020 100644 --- a/Telegram/SourceFiles/settingswidget.cpp +++ b/Telegram/SourceFiles/settingswidget.cpp @@ -894,6 +894,8 @@ void SettingsInner::offPasswordDone(const MTPBool &result) { } bool SettingsInner::offPasswordFail(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + onReloadPassword(); return true; } diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp new file mode 100644 index 0000000000..e6f47ea160 --- /dev/null +++ b/Telegram/SourceFiles/structs.cpp @@ -0,0 +1,633 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "style.h" +#include "lang.h" + +#include "history.h" +#include "mainwidget.h" +#include "application.h" +#include "fileuploader.h" +#include "window.h" +#include "gui/filedialog.h" + +#include "audio.h" +#include "localstorage.h" + +namespace { + int32 peerColorIndex(const PeerId &peer) { + int32 myId(MTP::authedId()), peerId(peer & 0xFFFFFFFFL); + bool chat = (peer & 0x100000000L); + if (chat) { + int ch = 0; + } + QByteArray both(qsl("%1%2").arg(peerId).arg(myId).toUtf8()); + if (both.size() > 15) { + both = both.mid(0, 15); + } + uchar md5[16]; + hashMd5(both.constData(), both.size(), md5); + return (md5[peerId & 0x0F] & (chat ? 0x03 : 0x07)); + } +} + +style::color peerColor(int32 index) { + static const style::color peerColors[8] = { + style::color(st::color1), + style::color(st::color2), + style::color(st::color3), + style::color(st::color4), + style::color(st::color5), + style::color(st::color6), + style::color(st::color7), + style::color(st::color8) + }; + return peerColors[index]; +} + +ImagePtr userDefPhoto(int32 index) { + static const ImagePtr userDefPhotos[8] = { + ImagePtr(qsl(":/ava/art/usercolor1.png"), "PNG"), + ImagePtr(qsl(":/ava/art/usercolor2.png"), "PNG"), + ImagePtr(qsl(":/ava/art/usercolor3.png"), "PNG"), + ImagePtr(qsl(":/ava/art/usercolor4.png"), "PNG"), + ImagePtr(qsl(":/ava/art/usercolor5.png"), "PNG"), + ImagePtr(qsl(":/ava/art/usercolor6.png"), "PNG"), + ImagePtr(qsl(":/ava/art/usercolor7.png"), "PNG"), + ImagePtr(qsl(":/ava/art/usercolor8.png"), "PNG"), + }; + return userDefPhotos[index]; +} + +ImagePtr chatDefPhoto(int32 index) { + static const ImagePtr chatDefPhotos[4] = { + ImagePtr(qsl(":/ava/art/chatcolor1.png"), "PNG"), + ImagePtr(qsl(":/ava/art/chatcolor2.png"), "PNG"), + ImagePtr(qsl(":/ava/art/chatcolor3.png"), "PNG"), + ImagePtr(qsl(":/ava/art/chatcolor4.png"), "PNG"), + }; + return chatDefPhotos[index]; +} + +NotifySettings globalNotifyAll, globalNotifyUsers, globalNotifyChats; +NotifySettingsPtr globalNotifyAllPtr = UnknownNotifySettings, globalNotifyUsersPtr = UnknownNotifySettings, globalNotifyChatsPtr = UnknownNotifySettings; + +PeerData::PeerData(const PeerId &id) : id(id) +, loaded(false) +, chat(App::isChat(id)) +, access(0) +, colorIndex(peerColorIndex(id)) +, color(peerColor(colorIndex)) +, photo(chat ? chatDefPhoto(colorIndex) : userDefPhoto(colorIndex)) +, nameVersion(0) +, notify(UnknownNotifySettings) +{ +} + +UserData *PeerData::asUser() { + return chat ? App::user(id & 0xFFFFFFFFL) : static_cast(this); +} + +const UserData *PeerData::asUser() const { + return chat ? App::user(id & 0xFFFFFFFFL) : static_cast(this); +} + +ChatData *PeerData::asChat() { + return chat ? static_cast(this) : App::chat(id | 0x100000000L); +} + +const ChatData *PeerData::asChat() const { + return chat ? static_cast(this) : App::chat(id | 0x100000000L); +} + +void PeerData::updateName(const QString &newName, const QString &newNameOrPhone, const QString &newUsername) { + if (name == newName && nameOrPhone == newNameOrPhone && (chat || asUser()->username == newUsername) && nameVersion > 0) return; + + ++nameVersion; + name = newName; + nameOrPhone = newNameOrPhone; + if (!chat) asUser()->username = newUsername; + Names oldNames = names; + NameFirstChars oldChars = chars; + fillNames(); + App::history(id)->updateNameText(); + if (App::main()) { + emit App::main()->peerNameChanged(this, oldNames, oldChars); + } + nameUpdated(); +} + +void UserData::setPhoto(const MTPUserProfilePhoto &p) { + switch (p.type()) { + case mtpc_userProfilePhoto: { + const MTPDuserProfilePhoto d(p.c_userProfilePhoto()); + photoId = d.vphoto_id.v; + photo = ImagePtr(160, 160, d.vphoto_small, userDefPhoto(colorIndex)); + // App::feedPhoto(App::photoFromUserPhoto(MTP_int(id & 0xFFFFFFFF), MTP_int(unixtime()), p)); + } break; + default: { + photoId = 0; + if (id == ServiceUserId) { + photo = ImagePtr(QPixmap::fromImage(App::wnd()->iconLarge().scaledToWidth(160, Qt::SmoothTransformation), Qt::ColorOnly), "PNG"); + } else { + photo = userDefPhoto(colorIndex); + } + } break; + } + emit App::main()->peerPhotoChanged(this); +} + +void PeerData::fillNames() { + names.clear(); + chars.clear(); + QString toIndex = textAccentFold(name); + if (nameOrPhone != name) { + toIndex += ' ' + textAccentFold(nameOrPhone); + } + if (!chat) { + toIndex += ' ' + textAccentFold(asUser()->username); + } + if (cRussianLetters().match(toIndex).hasMatch()) { + toIndex += ' ' + translitRusEng(toIndex); + } + toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex); + + QStringList namesList = toIndex.toLower().split(cWordSplit(), QString::SkipEmptyParts); + for (QStringList::const_iterator i = namesList.cbegin(), e = namesList.cend(); i != e; ++i) { + names.insert(*i); + chars.insert(i->at(0)); + } +} + + +void UserData::setName(const QString &first, const QString &last, const QString &phoneName, const QString &usern) { + bool updName = !first.isEmpty() || !last.isEmpty(), updUsername = (username != usern); + + if (updName && first.trimmed().isEmpty()) { + firstName = last; + lastName = QString(); + updateName(firstName, phoneName, usern); + } else { + if (updName) { + firstName = first; + lastName = last; + } + updateName(lastName.isEmpty() ? firstName : (firstName + ' ' + lastName), phoneName, usern); + } + if (updUsername) { + if (App::main()) { + App::main()->peerUsernameChanged(this); + } + } +} + +void UserData::setPhone(const QString &newPhone) { + phone = newPhone; + ++nameVersion; +} + +void UserData::nameUpdated() { + nameText.setText(st::msgNameFont, name, _textNameOptions); +} + +void ChatData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { + switch (p.type()) { + case mtpc_chatPhoto: { + const MTPDchatPhoto d(p.c_chatPhoto()); + photo = ImagePtr(160, 160, d.vphoto_small, chatDefPhoto(colorIndex)); + photoFull = ImagePtr(640, 640, d.vphoto_big, chatDefPhoto(colorIndex)); + if (phId) { + photoId = phId; + } + } break; + default: { + photo = chatDefPhoto(colorIndex); + photoFull = ImagePtr(); + photoId = 0; + } break; + } + emit App::main()->peerPhotoChanged(this); +} + +void PhotoLink::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton) { + App::wnd()->showPhoto(this, App::hoveredLinkItem()); + } +} + +QString saveFileName(const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir = QDir()) { +#ifdef Q_OS_WIN + name = name.replace(QRegularExpression(qsl("[\\\\\\/\\:\\*\\?\\\"\\<\\>\\|]")), qsl("_")); +#elif defined Q_OS_MAC + name = name.replace(QRegularExpression(qsl("[\\:]")), qsl("_")); +#elif defined Q_OS_LINUX + name = name.replace(QRegularExpression(qsl("[\\/]")), qsl("_")); +#endif + if (cAskDownloadPath() || savingAs) { + if (!name.isEmpty() && name.at(0) == QChar::fromLatin1('.')) { + name = filedialogDefaultName(prefix, name); + } else if (dir.path() != qsl(".")) { + QString path = dir.absolutePath(); + if (path != cDialogLastPath()) { + cSetDialogLastPath(path); + Local::writeUserSettings(); + } + } + + return filedialogGetSaveFile(name, title, filter, name) ? name : QString(); + } + + QString path; + if (cDownloadPath().isEmpty()) { + path = psDownloadPath(); + } else if (cDownloadPath() == qsl("tmp")) { + path = cTempDir(); + } else { + path = cDownloadPath(); + } + if (name.isEmpty()) name = qsl(".unknown"); + if (name.at(0) == QChar::fromLatin1('.')) { + if (!QDir().exists(path)) QDir().mkpath(path); + return filedialogDefaultName(prefix, name, path); + } + if (dir.path() != qsl(".")) { + path = dir.absolutePath() + '/'; + } + + QString nameStart, extension; + int32 extPos = name.lastIndexOf('.'); + if (extPos >= 0) { + nameStart = name.mid(0, extPos); + extension = name.mid(extPos); + } else { + nameStart = name; + } + QString nameBase = path + nameStart; + name = nameBase + extension; + for (int i = 0; QFileInfo(name).exists(); ++i) { + name = nameBase + QString(" (%1)").arg(i + 2) + extension; + } + + if (!QDir().exists(path)) QDir().mkpath(path); + return name; +} + +void VideoOpenLink::onClick(Qt::MouseButton button) const { + VideoData *data = video(); + if ((!data->user && !data->date) || button != Qt::LeftButton) return; + + QString already = data->already(true); + if (!already.isEmpty()) { + psOpenFile(already); + return; + } + + if (data->status != FileReady) return; + + QString filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), qsl(".mov"), false); + if (!filename.isEmpty()) { + data->openOnSave = 1; + data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0; + data->save(filename); + } +} + +void VideoSaveLink::doSave(bool forceSavingAs) const { + VideoData *data = video(); + if (!data->user && !data->date) return; + + QString already = data->already(true); + if (!already.isEmpty() && !forceSavingAs) { + QPoint pos(QCursor::pos()); + if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { + psOpenFile(already, true); + } + } else { + QFileInfo alreadyInfo(already); + QDir alreadyDir(already.isEmpty() ? QDir() : alreadyInfo.dir()); + QString name = already.isEmpty() ? QString(".mov") : alreadyInfo.fileName(); + QString filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), name, forceSavingAs, alreadyDir); + if (!filename.isEmpty()) { + if (forceSavingAs) { + data->cancel(); + } else if (!already.isEmpty()) { + data->openOnSave = -1; + data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0; + } + data->save(filename); + } + } +} + +void VideoSaveLink::onClick(Qt::MouseButton button) const { + if (button != Qt::LeftButton) return; + doSave(); +} + +void VideoCancelLink::onClick(Qt::MouseButton button) const { + VideoData *data = video(); + if ((!data->user && !data->date) || button != Qt::LeftButton) return; + + data->cancel(); +} + +VideoData::VideoData(const VideoId &id, const uint64 &access, int32 user, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size) : +id(id), access(access), user(user), date(date), duration(duration), w(w), h(h), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), fileType(0), openOnSave(0), openOnSaveMsgId(0), loader(0) { + location = Local::readFileLocation(mediaKey(mtpc_inputVideoFileLocation, dc, id)); +} + +void VideoData::save(const QString &toFile) { + cancel(true); + loader = new mtpFileLoader(dc, id, access, mtpc_inputVideoFileLocation, toFile, size); + loader->connect(loader, SIGNAL(progress(mtpFileLoader*)), App::main(), SLOT(videoLoadProgress(mtpFileLoader*))); + loader->connect(loader, SIGNAL(failed(mtpFileLoader*, bool)), App::main(), SLOT(videoLoadFailed(mtpFileLoader*, bool))); + loader->start(); +} + +QString VideoData::already(bool check) { + if (!check) return location.name; + if (!location.check()) location = Local::readFileLocation(mediaKey(mtpc_inputVideoFileLocation, dc, id)); + return location.name; +} + +void AudioOpenLink::onClick(Qt::MouseButton button) const { + AudioData *data = audio(); + if ((!data->user && !data->date) || button != Qt::LeftButton) return; + + bool mp3 = (data->mime == QLatin1String("audio/mp3")); + + QString already = data->already(true); + bool play = !mp3 && audioVoice(); + if (!already.isEmpty() || (!data->data.isEmpty() && play)) { + if (play) { + AudioData *playing = 0; + VoiceMessageState playingState = VoiceMessageStopped; + audioVoice()->currentState(&playing, &playingState); + if (playing == data && playingState != VoiceMessageStopped) { + audioVoice()->pauseresume(); + } else { + audioVoice()->play(data); + } + } else { + psOpenFile(already); + } + return; + } + + if (data->status != FileReady) return; + + QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), mp3 ? qsl(".mp3") : qsl(".ogg"), false); + if (!filename.isEmpty()) { + data->openOnSave = 1; + data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0; + data->save(filename); + } +} + +void AudioSaveLink::doSave(bool forceSavingAs) const { + AudioData *data = audio(); + if (!data->user && !data->date) return; + + QString already = data->already(true); + if (!already.isEmpty() && !forceSavingAs) { + QPoint pos(QCursor::pos()); + if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { + psOpenFile(already, true); + } + } else { + QFileInfo alreadyInfo(already); + QDir alreadyDir(already.isEmpty() ? QDir() : alreadyInfo.dir()); + bool mp3 = (data->mime == QLatin1String("audio/mp3")); + QString name = already.isEmpty() ? (mp3 ? qsl(".mp3") : qsl(".ogg")) : alreadyInfo.fileName(); + QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), name, forceSavingAs, alreadyDir); + if (!filename.isEmpty()) { + if (forceSavingAs) { + data->cancel(); + } else if (!already.isEmpty()) { + data->openOnSave = -1; + data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0; + } + data->save(filename); + } + } +} + +void AudioSaveLink::onClick(Qt::MouseButton button) const { + if (button != Qt::LeftButton) return; + doSave(); +} + +void AudioCancelLink::onClick(Qt::MouseButton button) const { + AudioData *data = audio(); + if ((!data->user && !data->date) || button != Qt::LeftButton) return; + + data->cancel(); +} + +AudioData::AudioData(const AudioId &id, const uint64 &access, int32 user, int32 date, const QString &mime, int32 duration, int32 dc, int32 size) : +id(id), access(access), user(user), date(date), mime(mime), duration(duration), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), openOnSaveMsgId(0), loader(0) { + location = Local::readFileLocation(mediaKey(mtpc_inputAudioFileLocation, dc, id)); +} + +void AudioData::save(const QString &toFile) { + cancel(true); + loader = new mtpFileLoader(dc, id, access, mtpc_inputAudioFileLocation, toFile, size, (size < AudioVoiceMsgInMemory)); + loader->connect(loader, SIGNAL(progress(mtpFileLoader*)), App::main(), SLOT(audioLoadProgress(mtpFileLoader*))); + loader->connect(loader, SIGNAL(failed(mtpFileLoader*, bool)), App::main(), SLOT(audioLoadFailed(mtpFileLoader*, bool))); + loader->start(); +} + +QString AudioData::already(bool check) { + if (!check) return location.name; + if (!location.check()) location = Local::readFileLocation(mediaKey(mtpc_inputAudioFileLocation, dc, id)); + return location.name; +} + +void DocumentOpenLink::onClick(Qt::MouseButton button) const { + DocumentData *data = document(); + if (!data->date || button != Qt::LeftButton) return; + + QString already = data->already(true); + if (!already.isEmpty()) { + if (data->size < MediaViewImageSizeLimit) { + QImageReader reader(already); + if (reader.canRead()) { + if (reader.supportsAnimation() && reader.imageCount() > 1 && App::hoveredLinkItem()) { + startGif(App::hoveredLinkItem(), already); + } else { + App::wnd()->showDocument(data, QPixmap::fromImage(App::readImage(already, 0, false), Qt::ColorOnly), App::hoveredLinkItem()); + } + } else { + psOpenFile(already); + } + } else { + psOpenFile(already); + } + return; + } + + if (data->status != FileReady) return; + + QString name = data->name, filter; + MimeType mimeType = mimeTypeForName(data->mime); + QStringList p = mimeType.globPatterns(); + QString pattern = p.isEmpty() ? QString() : p.front(); + if (name.isEmpty()) { + name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString()); + } + + if (pattern.isEmpty()) { + filter = qsl("All files (*.*)"); + } else { + filter = mimeType.filterString() + qsl(";;All files (*.*)"); + } + + QString filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, false); + if (!filename.isEmpty()) { + data->openOnSave = 1; + data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0; + data->save(filename); + } +} + +void DocumentSaveLink::doSave(bool forceSavingAs) const { + DocumentData *data = document(); + if (!data->date) return; + + QString already = data->already(true); + if (!already.isEmpty() && !forceSavingAs) { + QPoint pos(QCursor::pos()); + if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { + psOpenFile(already, true); + } + } else { + QFileInfo alreadyInfo(already); + QDir alreadyDir(already.isEmpty() ? QDir() : alreadyInfo.dir()); + QString name = already.isEmpty() ? data->name : alreadyInfo.fileName(), filter; + MimeType mimeType = mimeTypeForName(data->mime); + QStringList p = mimeType.globPatterns(); + QString pattern = p.isEmpty() ? QString() : p.front(); + if (name.isEmpty()) { + name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString()); + } + + if (pattern.isEmpty()) { + filter = qsl("All files (*.*)"); + } else { + filter = mimeType.filterString() + qsl(";;All files (*.*)"); + } + + QString filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, forceSavingAs, alreadyDir); + if (!filename.isEmpty()) { + if (forceSavingAs) { + data->cancel(); + } else if (!already.isEmpty()) { + data->openOnSave = -1; + data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0; + } + data->save(filename); + } + } +} + +void DocumentSaveLink::onClick(Qt::MouseButton button) const { + if (button != Qt::LeftButton) return; + doSave(); +} + +void DocumentCancelLink::onClick(Qt::MouseButton button) const { + DocumentData *data = document(); + if (!data->date || button != Qt::LeftButton) return; + + data->cancel(); +} + +DocumentData::DocumentData(const DocumentId &id, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size) : +id(id), type(FileDocument), duration(0), access(access), date(date), mime(mime), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), openOnSaveMsgId(0), loader(0) { + setattributes(attributes); + location = Local::readFileLocation(mediaKey(mtpc_inputDocumentFileLocation, dc, id)); +} + +void DocumentData::setattributes(const QVector &attributes) { + for (int32 i = 0, l = attributes.size(); i < l; ++i) { + switch (attributes[i].type()) { + case mtpc_documentAttributeImageSize: { + const MTPDdocumentAttributeImageSize &d(attributes[i].c_documentAttributeImageSize()); + dimensions = QSize(d.vw.v, d.vh.v); + } break; + case mtpc_documentAttributeAnimated: if (type == FileDocument || type == StickerDocument) type = AnimatedDocument; break; + case mtpc_documentAttributeSticker: { + const MTPDdocumentAttributeSticker &d(attributes[i].c_documentAttributeSticker()); + if (type == FileDocument) type = StickerDocument; + alt = qs(d.valt); + } break; + case mtpc_documentAttributeVideo: { + const MTPDdocumentAttributeVideo &d(attributes[i].c_documentAttributeVideo()); + type = VideoDocument; + duration = d.vduration.v; + dimensions = QSize(d.vw.v, d.vh.v); + } break; + case mtpc_documentAttributeAudio: { + const MTPDdocumentAttributeAudio &d(attributes[i].c_documentAttributeAudio()); + type = AudioDocument; + duration = d.vduration.v; + } break; + case mtpc_documentAttributeFilename: name = qs(attributes[i].c_documentAttributeFilename().vfile_name); break; + } + } +} + +void DocumentData::save(const QString &toFile) { + cancel(true); + bool isSticker = (type == StickerDocument) && (dimensions.width() > 0) && (dimensions.height() > 0) && (size < StickerInMemory); + loader = new mtpFileLoader(dc, id, access, mtpc_inputDocumentFileLocation, toFile, size, isSticker); + loader->connect(loader, SIGNAL(progress(mtpFileLoader*)), App::main(), SLOT(documentLoadProgress(mtpFileLoader*))); + loader->connect(loader, SIGNAL(failed(mtpFileLoader*, bool)), App::main(), SLOT(documentLoadFailed(mtpFileLoader*, bool))); + loader->start(); +} + +QString DocumentData::already(bool check) { + if (!check) return location.name; + if (!location.check()) location = Local::readFileLocation(mediaKey(mtpc_inputDocumentFileLocation, dc, id)); + return location.name; +} + +WebPageData::WebPageData(const WebPageId &id, WebPageType type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const QString &description, PhotoData *photo, int32 duration, const QString &author, int32 pendingTill) : +id(id), type(type), url(url), displayUrl(displayUrl), siteName(siteName), title(title), description(description), photo(photo), duration(duration), author(author), pendingTill(pendingTill) { +} + +void PeerLink::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton && App::main()) { + App::main()->showPeerProfile(peer()); + } +} + +void MessageLink::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton && App::main()) { + HistoryItem *current = App::mousedItem(); + if (current && current->history()->peer->id == peer()) { + App::main()->pushReplyReturn(current); + } + App::main()->showPeer(peer(), msgid()); + } +} + +MsgId clientMsgId() { + static MsgId current = -2000000000; + return ++current; +} diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h new file mode 100644 index 0000000000..4f627f340c --- /dev/null +++ b/Telegram/SourceFiles/structs.h @@ -0,0 +1,546 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014 John Preston, https://desktop.telegram.org +*/ +#pragma once + +typedef uint64 PeerId; +typedef uint64 PhotoId; +typedef uint64 VideoId; +typedef uint64 AudioId; +typedef uint64 DocumentId; +typedef uint64 WebPageId; +typedef int32 MsgId; + +struct NotifySettings { + NotifySettings() : mute(0), sound("default"), previews(true), events(1) { + } + int32 mute; + string sound; + bool previews; + int32 events; +}; +typedef NotifySettings *NotifySettingsPtr; + +static const NotifySettingsPtr UnknownNotifySettings = NotifySettingsPtr(0); +static const NotifySettingsPtr EmptyNotifySettings = NotifySettingsPtr(1); +extern NotifySettings globalNotifyAll, globalNotifyUsers, globalNotifyChats; +extern NotifySettingsPtr globalNotifyAllPtr, globalNotifyUsersPtr, globalNotifyChatsPtr; + +inline bool isNotifyMuted(NotifySettingsPtr settings) { + if (settings == UnknownNotifySettings || settings == EmptyNotifySettings) { + return false; + } + return (settings->mute > unixtime()); +} + +style::color peerColor(int32 index); +ImagePtr userDefPhoto(int32 index); +ImagePtr chatDefPhoto(int32 index); + +struct ChatData; +struct UserData; +struct PeerData { + PeerData(const PeerId &id); + virtual ~PeerData() { + if (notify != UnknownNotifySettings && notify != EmptyNotifySettings) { + delete notify; + notify = UnknownNotifySettings; + } + } + + UserData *asUser(); + const UserData *asUser() const; + + ChatData *asChat(); + const ChatData *asChat() const; + + void updateName(const QString &newName, const QString &newNameOrPhone, const QString &newUsername); + + void fillNames(); + + virtual void nameUpdated() { + } + + PeerId id; + + QString name; + QString nameOrPhone; + typedef QSet Names; + Names names; // for filtering + typedef QSet NameFirstChars; + NameFirstChars chars; + + bool loaded; + bool chat; + uint64 access; + MTPinputPeer input; + MTPinputUser inputUser; + + int32 colorIndex; + style::color color; + ImagePtr photo; + + int32 nameVersion; + + NotifySettingsPtr notify; +}; +static const uint64 UserNoAccess = 0xFFFFFFFFFFFFFFFFULL; + +class PeerLink : public ITextLink { +public: + PeerLink(PeerData *peer) : _peer(peer) { + } + void onClick(Qt::MouseButton button) const; + PeerData *peer() const { + return _peer; + } + +private: + PeerData *_peer; +}; + +struct PhotoData; +struct UserData : public PeerData { + UserData(const PeerId &id) : PeerData(id), lnk(new PeerLink(this)), onlineTill(0), contact(-1), photosCount(-1) { + } + void setPhoto(const MTPUserProfilePhoto &photo); + void setName(const QString &first, const QString &last, const QString &phoneName, const QString &username); + void setPhone(const QString &newPhone); + void nameUpdated(); + + QString firstName; + QString lastName; + QString username; + QString phone; + Text nameText; + PhotoId photoId; + TextLinkPtr lnk; + int32 onlineTill; + int32 contact; // -1 - not contact, cant add (self, empty, deleted, foreign), 0 - not contact, can add (request), 1 - contact + + typedef QList Photos; + Photos photos; + int32 photosCount; // -1 not loaded, 0 all loaded +}; + +struct ChatData : public PeerData { + ChatData(const PeerId &id) : PeerData(id), count(0), date(0), version(0), left(false), forbidden(true), photoId(0) { + } + void setPhoto(const MTPChatPhoto &photo, const PhotoId &phId = 0); + int32 count; + int32 date; + int32 version; + int32 admin; + bool left; + bool forbidden; + typedef QMap Participants; + Participants participants; + typedef QMap CanKick; + CanKick cankick; + typedef QList LastAuthors; + LastAuthors lastAuthors; + ImagePtr photoFull; + PhotoId photoId; + // geo +}; + +typedef QMap PreparedPhotoThumbs; +struct PhotoData { + PhotoData(const PhotoId &id, const uint64 &access = 0, int32 user = 0, int32 date = 0, const ImagePtr &thumb = ImagePtr(), const ImagePtr &medium = ImagePtr(), const ImagePtr &full = ImagePtr()) : + id(id), access(access), user(user), date(date), thumb(thumb), medium(medium), full(full), chat(0) { + } + void forget() { + thumb->forget(); + replyPreview->forget(); + medium->forget(); + full->forget(); + } + PhotoId id; + uint64 access; + int32 user; + int32 date; + ImagePtr thumb, replyPreview; + ImagePtr medium; + ImagePtr full; + ChatData *chat; // for chat photos connection + // geo, caption + + int32 cachew; + QPixmap cache; +}; + +class PhotoLink : public ITextLink { +public: + PhotoLink(PhotoData *photo) : _photo(photo), _peer(0) { + } + PhotoLink(PhotoData *photo, PeerData *peer) : _photo(photo), _peer(peer) { + } + void onClick(Qt::MouseButton button) const; + PhotoData *photo() const { + return _photo; + } + PeerData *peer() const { + return _peer; + } + +private: + PhotoData *_photo; + PeerData *_peer; +}; + +enum FileStatus { + FileFailed = -1, + FileUploading = 0, + FileReady = 1, +}; + +struct VideoData { + VideoData(const VideoId &id, const uint64 &access = 0, int32 user = 0, int32 date = 0, int32 duration = 0, int32 w = 0, int32 h = 0, const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0); + + void forget() { + thumb->forget(); + replyPreview->forget(); + } + + void save(const QString &toFile); + + void cancel(bool beforeDownload = false) { + mtpFileLoader *l = loader; + loader = 0; + if (l) { + l->cancel(); + l->deleteLater(); + l->rpcInvalidate(); + } + location = FileLocation(); + if (!beforeDownload) { + openOnSave = openOnSaveMsgId = 0; + } + } + + void finish() { + if (loader->done()) { + location = FileLocation(loader->fileType(), loader->fileName()); + } + loader->deleteLater(); + loader->rpcInvalidate(); + loader = 0; + } + + QString already(bool check = false); + + VideoId id; + uint64 access; + int32 user; + int32 date; + int32 duration; + int32 w, h; + ImagePtr thumb, replyPreview; + int32 dc, size; + // geo, caption + + FileStatus status; + int32 uploadOffset; + + mtpTypeId fileType; + int32 openOnSave, openOnSaveMsgId; + mtpFileLoader *loader; + FileLocation location; +}; + +class VideoLink : public ITextLink { +public: + VideoLink(VideoData *video) : _video(video) { + } + VideoData *video() const { + return _video; + } + +private: + VideoData *_video; +}; + +class VideoSaveLink : public VideoLink { +public: + VideoSaveLink(VideoData *video) : VideoLink(video) { + } + void doSave(bool forceSavingAs = false) const; + void onClick(Qt::MouseButton button) const; +}; + +class VideoOpenLink : public VideoLink { +public: + VideoOpenLink(VideoData *video) : VideoLink(video) { + } + void onClick(Qt::MouseButton button) const; +}; + +class VideoCancelLink : public VideoLink { +public: + VideoCancelLink(VideoData *video) : VideoLink(video) { + } + void onClick(Qt::MouseButton button) const; +}; + +struct AudioData { + AudioData(const AudioId &id, const uint64 &access = 0, int32 user = 0, int32 date = 0, const QString &mime = QString(), int32 duration = 0, int32 dc = 0, int32 size = 0); + + void forget() { + } + + void save(const QString &toFile); + + void cancel(bool beforeDownload = false) { + mtpFileLoader *l = loader; + loader = 0; + if (l) { + l->cancel(); + l->deleteLater(); + l->rpcInvalidate(); + } + location = FileLocation(); + if (!beforeDownload) { + openOnSave = openOnSaveMsgId = 0; + } + } + + void finish() { + if (loader->done()) { + location = FileLocation(loader->fileType(), loader->fileName()); + data = loader->bytes(); + } + loader->deleteLater(); + loader->rpcInvalidate(); + loader = 0; + } + + QString already(bool check = false); + + AudioId id; + uint64 access; + int32 user; + int32 date; + QString mime; + int32 duration; + int32 dc; + int32 size; + + FileStatus status; + int32 uploadOffset; + + int32 openOnSave, openOnSaveMsgId; + mtpFileLoader *loader; + FileLocation location; + QByteArray data; + int32 md5[8]; +}; + +class AudioLink : public ITextLink { +public: + AudioLink(AudioData *audio) : _audio(audio) { + } + AudioData *audio() const { + return _audio; + } + +private: + AudioData *_audio; +}; + +class AudioSaveLink : public AudioLink { +public: + AudioSaveLink(AudioData *audio) : AudioLink(audio) { + } + void doSave(bool forceSavingAs = false) const; + void onClick(Qt::MouseButton button) const; +}; + +class AudioOpenLink : public AudioLink { +public: + AudioOpenLink(AudioData *audio) : AudioLink(audio) { + } + void onClick(Qt::MouseButton button) const; +}; + +class AudioCancelLink : public AudioLink { +public: + AudioCancelLink(AudioData *audio) : AudioLink(audio) { + } + void onClick(Qt::MouseButton button) const; +}; + +enum DocumentType { + FileDocument, + VideoDocument, + AudioDocument, + StickerDocument, + AnimatedDocument +}; +struct DocumentData { + DocumentData(const DocumentId &id, const uint64 &access = 0, int32 date = 0, const QVector &attributes = QVector(), const QString &mime = QString(), const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0); + void setattributes(const QVector &attributes); + + void forget() { + thumb->forget(); + sticker->forget(); + replyPreview->forget(); + } + + void save(const QString &toFile); + + void cancel(bool beforeDownload = false) { + mtpFileLoader *l = loader; + loader = 0; + if (l) { + l->cancel(); + l->deleteLater(); + l->rpcInvalidate(); + } + location = FileLocation(); + if (!beforeDownload) { + openOnSave = openOnSaveMsgId = 0; + } + } + + void finish() { + if (loader->done()) { + location = FileLocation(loader->fileType(), loader->fileName()); + data = loader->bytes(); + } + loader->deleteLater(); + loader->rpcInvalidate(); + loader = 0; + } + + QString already(bool check = false); + + DocumentId id; + DocumentType type; + QSize dimensions; + int32 duration; + uint64 access; + int32 date; + QString name, mime, alt; // alt - for stickers + ImagePtr thumb, replyPreview; + int32 dc; + int32 size; + + FileStatus status; + int32 uploadOffset; + + int32 openOnSave, openOnSaveMsgId; + mtpFileLoader *loader; + FileLocation location; + + QByteArray data; + ImagePtr sticker; + + int32 md5[8]; +}; + +class DocumentLink : public ITextLink { +public: + DocumentLink(DocumentData *document) : _document(document) { + } + DocumentData *document() const { + return _document; + } + +private: + DocumentData *_document; +}; + +class DocumentSaveLink : public DocumentLink { +public: + DocumentSaveLink(DocumentData *document) : DocumentLink(document) { + } + void doSave(bool forceSavingAs = false) const; + void onClick(Qt::MouseButton button) const; +}; + +class DocumentOpenLink : public DocumentLink { +public: + DocumentOpenLink(DocumentData *document) : DocumentLink(document) { + } + void onClick(Qt::MouseButton button) const; +}; + +class DocumentCancelLink : public DocumentLink { +public: + DocumentCancelLink(DocumentData *document) : DocumentLink(document) { + } + void onClick(Qt::MouseButton button) const; +}; + +enum WebPageType { + WebPagePhoto, + WebPageVideo, + WebPageProfile, + WebPageArticle +}; +inline WebPageType toWebPageType(const QString &type) { + if (type == QLatin1String("photo")) return WebPagePhoto; + if (type == QLatin1String("video")) return WebPageVideo; + if (type == QLatin1String("profile")) return WebPageProfile; + return WebPageArticle; +} + +struct WebPageData { + WebPageData(const WebPageId &id, WebPageType type = WebPageArticle, const QString &url = QString(), const QString &displayUrl = QString(), const QString &siteName = QString(), const QString &title = QString(), const QString &description = QString(), PhotoData *photo = 0, int32 duration = 0, const QString &author = QString(), int32 pendingTill = 0); + + void forget() { + if (photo) photo->forget(); + } + + WebPageId id; + WebPageType type; + QString url, displayUrl, siteName, title, description; + int32 duration; + QString author; + PhotoData *photo; + int32 pendingTill; +}; + +MsgId clientMsgId(); + +struct MessageCursor { + MessageCursor() : position(0), anchor(0), scroll(QFIXED_MAX) { + } + MessageCursor(int position, int anchor, int scroll) : position(position), anchor(anchor), scroll(scroll) { + } + MessageCursor(const QTextEdit &edit) { + fillFrom(edit); + } + void fillFrom(const QTextEdit &edit) { + QTextCursor c = edit.textCursor(); + position = c.position(); + anchor = c.anchor(); + QScrollBar *s = edit.verticalScrollBar(); + scroll = s ? s->value() : QFIXED_MAX; + } + void applyTo(QTextEdit &edit, bool *lock = 0) { + if (lock) *lock = true; + QTextCursor c = edit.textCursor(); + c.setPosition(anchor, QTextCursor::MoveAnchor); + c.setPosition(position, QTextCursor::KeepAnchor); + edit.setTextCursor(c); + QScrollBar *s = edit.verticalScrollBar(); + if (s) s->setValue(scroll); + if (lock) *lock = false; + } + int position, anchor, scroll; +}; diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index c333ada459..c30453a60f 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -1019,6 +1019,7 @@ Create Create + @@ -1961,6 +1962,7 @@ true + Moc%27ing sysbuttons.h... diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 842801a95f..d403d05bcc 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -870,6 +870,9 @@ Generated Files\Release + + Source Files + @@ -953,6 +956,9 @@ Source Files + + Source Files +