diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7b9adcfee..041fd7827 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -405,12 +405,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_actions_section" = "Actions"; "lng_profile_bot_settings" = "Settings"; "lng_profile_bot_help" = "Help"; +"lng_profile_invite_link_section" = "Invite link"; "lng_profile_create_public_link" = "Create public link"; "lng_profile_edit_public_link" = "Edit public link"; "lng_profile_participants_section" = "Members"; -"lng_profile_info" = "Contact info"; -"lng_profile_group_info" = "Group info"; -"lng_profile_channel_info" = "Channel info"; +"lng_profile_info_section" = "Info"; +"lng_profile_mobile_number" = "Mobile:"; +"lng_profile_username" = "Username:"; +"lng_profile_link" = "Link:"; "lng_profile_add_contact" = "Add Contact"; "lng_profile_edit_contact" = "Edit"; "lng_profile_enable_notifications" = "Notifications"; @@ -912,6 +914,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org // Not used "lng_topbar_info" = "Info"; +"lng_profile_group_info" = "Group info"; +"lng_profile_channel_info" = "Channel info"; // Wnd specific diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 751757c63..11f67b836 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -276,7 +276,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt } break; } } - channel->about = qs(f.vabout); + channel->setAbout(qs(f.vabout)); int32 newCount = f.has_participants_count() ? f.vparticipants_count.v : 0; if (newCount != channel->count) { if (channel->isMegagroup() && !channel->mgInfo->lastParticipants.isEmpty()) { @@ -343,7 +343,7 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestI peer->asUser()->setBotInfoVersion(-1); } peer->asUser()->blocked = d.is_blocked() ? UserIsBlocked : UserIsNotBlocked; - peer->asUser()->about = d.has_about() ? qs(d.vabout) : QString(); + peer->asUser()->setAbout(d.has_about() ? qs(d.vabout) : QString()); if (req) { QMap<PeerData*, mtpRequestId>::iterator i = _fullPeerRequests.find(peer); diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index be22abcf3..c157dddc8 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -428,7 +428,10 @@ namespace { } } if (d.is_deleted()) { - data->setPhone(QString()); + if (!data->phone().isEmpty()) { + data->setPhone(QString()); + update.flags |= UpdateFlag::UserPhoneChanged; + } data->setNameDelayed(lang(lng_deleted), QString(), QString(), QString()); data->setPhoto(MTP_userProfilePhotoEmpty()); data->access = UserNoAccess; @@ -440,12 +443,14 @@ namespace { QString fname = (!minimal || noLocalName) ? (d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString()) : data->firstName; QString lname = (!minimal || noLocalName) ? (d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString()) : data->lastName; - QString phone = minimal ? data->phone : (d.has_phone() ? qs(d.vphone) : QString()); + QString phone = minimal ? data->phone() : (d.has_phone() ? qs(d.vphone) : QString()); QString uname = minimal ? data->username : (d.has_username() ? textOneLine(qs(d.vusername)) : QString()); - bool phoneChanged = (data->phone != phone); - if (phoneChanged) data->setPhone(phone); - + bool phoneChanged = (data->phone() != phone); + if (phoneChanged) { + data->setPhone(phone); + update.flags |= UpdateFlag::UserPhoneChanged; + } bool nameChanged = (data->firstName != fname) || (data->lastName != lname); bool showPhone = !isServiceUser(data->id) && !d.is_self() && !d.is_contact() && !d.is_mutual_contact(); @@ -480,7 +485,7 @@ namespace { } else { data->setBotInfoVersion(-1); } - data->contact = (d.is_contact() || d.is_mutual_contact()) ? 1 : (data->phone.isEmpty() ? -1 : 0); + data->contact = (d.is_contact() || d.is_mutual_contact()) ? 1 : (data->phone().isEmpty() ? -1 : 0); if (data->contact == 1 && cReportSpamStatuses().value(data->id, dbiprsHidden) != dbiprsHidden) { cRefReportSpamStatuses().insert(data->id, dbiprsHidden); Local::writeReportSpamStatuses(); @@ -522,7 +527,7 @@ namespace { case mtpc_userStatusOnline: data->onlineTill = status->c_userStatusOnline().vexpires.v; break; } - if (data->contact < 0 && !data->phone.isEmpty() && peerToUser(data->id) != MTP::authedId()) { + if (data->contact < 0 && !data->phone().isEmpty() && peerToUser(data->id) != MTP::authedId()) { data->contact = 0; } if (App::main()) { @@ -1259,7 +1264,7 @@ namespace { break; } if (user->contact < 1) { - if (user->contact < 0 && !user->phone.isEmpty() && peerToUser(user->id) != MTP::authedId()) { + if (user->contact < 0 && !user->phone().isEmpty() && peerToUser(user->id) != MTP::authedId()) { user->contact = 0; } } @@ -1276,7 +1281,7 @@ namespace { bool showPhone = !isServiceUser(user->id) && !user->isSelf() && !user->contact; bool showPhoneChanged = !isServiceUser(user->id) && !user->isSelf() && ((showPhone && !wasShowPhone) || (!showPhone && wasShowPhone)); if (showPhoneChanged) { - user->setNameDelayed(textOneLine(user->firstName), textOneLine(user->lastName), showPhone ? App::formatPhone(user->phone) : QString(), textOneLine(user->username)); + user->setNameDelayed(textOneLine(user->firstName), textOneLine(user->lastName), showPhone ? App::formatPhone(user->phone()) : QString(), textOneLine(user->username)); } markPeerUpdated(user); } diff --git a/Telegram/SourceFiles/boxes/aboutbox.cpp b/Telegram/SourceFiles/boxes/aboutbox.cpp index c5bc8cdf5..7e64bd52d 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.cpp +++ b/Telegram/SourceFiles/boxes/aboutbox.cpp @@ -32,9 +32,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org AboutBox::AboutBox() : AbstractBox(st::aboutWidth) , _version(this, lng_about_version(lt_version, QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString())), st::aboutVersionLink) -, _text1(this, lang(lng_about_text_1), st::aboutLabel, st::aboutTextStyle) -, _text2(this, lang(lng_about_text_2), st::aboutLabel, st::aboutTextStyle) -, _text3(this, QString(), st::aboutLabel, st::aboutTextStyle) +, _text1(this, lang(lng_about_text_1), FlatLabel::InitType::Rich, st::aboutLabel, st::aboutTextStyle) +, _text2(this, lang(lng_about_text_2), FlatLabel::InitType::Rich, st::aboutLabel, st::aboutTextStyle) +, _text3(this,st::aboutLabel, st::aboutTextStyle) , _done(this, lang(lng_close), st::defaultBoxButton) { _text3.setRichText(lng_about_text_3(lt_faq_open, qsl("[a href=\"%1\"]").arg(telegramFaqLink()), lt_faq_close, qsl("[/a]"))); diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index f1bea515d..abe636529 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -33,16 +33,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "observer_peer.h" AddContactBox::AddContactBox(QString fname, QString lname, QString phone) : AbstractBox(st::boxWidth) -, _user(0) , _save(this, lang(lng_add_contact), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _retry(this, lang(lng_try_other_contact), st::defaultBoxButton) , _first(this, st::defaultInputField, lang(lng_signup_firstname), fname) , _last(this, st::defaultInputField, lang(lng_signup_lastname), lname) , _phone(this, st::defaultInputField, lang(lng_contact_phone), phone) -, _invertOrder(langFirstNameGoesSecond()) -, _contactId(0) -, _addRequest(0) { +, _invertOrder(langFirstNameGoesSecond()) { if (!phone.isEmpty()) { _phone.setDisabled(true); } @@ -57,10 +54,8 @@ AddContactBox::AddContactBox(UserData *user) : AbstractBox(st::boxWidth) , _retry(this, lang(lng_try_other_contact), st::defaultBoxButton) , _first(this, st::defaultInputField, lang(lng_signup_firstname), user->firstName) , _last(this, st::defaultInputField, lang(lng_signup_lastname), user->lastName) -, _phone(this, st::defaultInputField, lang(lng_contact_phone), user->phone) -, _invertOrder(langFirstNameGoesSecond()) -, _contactId(0) -, _addRequest(0) { +, _phone(this, st::defaultInputField, lang(lng_contact_phone), user->phone()) +, _invertOrder(langFirstNameGoesSecond()) { _phone.setDisabled(true); initBox(); } @@ -191,7 +186,7 @@ void AddContactBox::onSave() { _sentName = firstName; if (_user) { _contactId = rand_value<uint64>(); - QVector<MTPInputContact> v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(_user->phone), MTP_string(firstName), MTP_string(lastName))); + QVector<MTPInputContact> v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(_user->phone()), MTP_string(firstName), MTP_string(lastName))); _addRequest = MTP::send(MTPcontacts_ImportContacts(MTP_vector<MTPInputContact>(v), MTP_bool(false)), rpcDone(&AddContactBox::onSaveUserDone), rpcFail(&AddContactBox::onSaveUserFail)); } else { _contactId = rand_value<uint64>(); @@ -1181,7 +1176,7 @@ EditChannelBox::EditChannelBox(ChannelData *channel) : AbstractBox() , _save(this, lang(lng_settings_save), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _title(this, st::defaultInputField, lang(lng_dlg_new_channel_name), _channel->name) -, _description(this, st::newGroupDescription, lang(lng_create_group_description), _channel->about) +, _description(this, st::newGroupDescription, lang(lng_create_group_description), _channel->about()) , _sign(this, lang(lng_edit_sign_messages), channel->addsSignature()) , _publicLink(this, lang(channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link), st::defaultBoxLinkButton) , _saveTitleRequestId(0) @@ -1322,7 +1317,7 @@ void EditChannelBox::onPublicLink() { } void EditChannelBox::saveDescription() { - if (_sentDescription == _channel->about) { + if (_sentDescription == _channel->about()) { saveSign(); } else { _saveDescriptionRequestId = MTP::send(MTPchannels_EditAbout(_channel->inputChannel, MTP_string(_sentDescription)), rpcDone(&EditChannelBox::onSaveDescriptionDone), rpcFail(&EditChannelBox::onSaveFail)); @@ -1357,9 +1352,11 @@ bool EditChannelBox::onSaveFail(const RPCError &error, mtpRequestId req) { } else if (req == _saveDescriptionRequestId) { _saveDescriptionRequestId = 0; if (err == qstr("CHAT_ABOUT_NOT_MODIFIED")) { - _channel->about = _sentDescription; - if (App::api()) { - emit App::api()->fullPeerUpdated(_channel); + if (_channel->setAbout(_sentDescription)) { + if (App::api()) { + emit App::api()->fullPeerUpdated(_channel); + } + Notify::peerUpdatedSendDelayed(); } saveSign(); return true; @@ -1386,9 +1383,11 @@ void EditChannelBox::onSaveTitleDone(const MTPUpdates &updates) { void EditChannelBox::onSaveDescriptionDone(const MTPBool &result) { _saveDescriptionRequestId = 0; - _channel->about = _sentDescription; - if (App::api()) { - emit App::api()->fullPeerUpdated(_channel); + if (_channel->setAbout(_sentDescription)) { + if (App::api()) { + emit App::api()->fullPeerUpdated(_channel); + } + Notify::peerUpdatedSendDelayed(); } saveSign(); } diff --git a/Telegram/SourceFiles/boxes/addcontactbox.h b/Telegram/SourceFiles/boxes/addcontactbox.h index 90e55311c..e70b1ac93 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.h +++ b/Telegram/SourceFiles/boxes/addcontactbox.h @@ -57,7 +57,7 @@ private: void initBox(); - UserData *_user; + UserData *_user = nullptr; QString _boxTitle; BoxButton _save, _cancel, _retry; @@ -66,9 +66,9 @@ private: bool _invertOrder; - uint64 _contactId; + uint64 _contactId = 0; - mtpRequestId _addRequest; + mtpRequestId _addRequest = 0; QString _sentName; }; diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index fcd6b5e1d..f4c044e53 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -381,11 +381,10 @@ void ConvertToSupergroupBox::resizeEvent(QResizeEvent *e) { PinMessageBox::PinMessageBox(ChannelData *channel, MsgId msgId) : AbstractBox(st::boxWidth) , _channel(channel) , _msgId(msgId) -, _text(this, lang(lng_pinned_pin_sure), st::boxLabel) +, _text(this, lang(lng_pinned_pin_sure), FlatLabel::InitType::Simple, st::boxLabel) , _notify(this, lang(lng_pinned_notify), true) , _pin(this, lang(lng_pinned_pin), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _requestId(0) { +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { _text.resizeToWidth(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()); setMaxHeight(st::boxPadding.top() + _text.height() + st::boxMediumSkip + _notify.height() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _pin.height() + st::boxButtonPadding.bottom()); @@ -441,7 +440,7 @@ RichDeleteMessageBox::RichDeleteMessageBox(ChannelData *channel, UserData *from, , _channel(channel) , _from(from) , _msgId(msgId) -, _text(this, lang(lng_selected_delete_sure_this), st::boxLabel) +, _text(this, lang(lng_selected_delete_sure_this), FlatLabel::InitType::Simple, st::boxLabel) , _banUser(this, lang(lng_ban_user), false) , _reportSpam(this, lang(lng_report_spam), false) , _deleteAll(this, lang(lng_delete_all_from), false) diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index 4af3c49af..e7af61470 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -217,7 +217,7 @@ private: BoxButton _pin, _cancel; - mtpRequestId _requestId; + mtpRequestId _requestId = 0; }; diff --git a/Telegram/SourceFiles/core/click_handler.h b/Telegram/SourceFiles/core/click_handler.h index c0199fac9..8ceadfb81 100644 --- a/Telegram/SourceFiles/core/click_handler.h +++ b/Telegram/SourceFiles/core/click_handler.h @@ -27,6 +27,7 @@ enum ExpandLinksMode { ExpandLinksNone, ExpandLinksShortened, ExpandLinksAll, + ExpandLinksUrlOnly, // For custom urls leaves only url instead of text. }; class ClickHandlerHost { diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 05043abf2..23b34fb76 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -110,15 +110,22 @@ QString HiddenUrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const Q QString result; if (mode == ExpandLinksAll) { result = textPart.toString() + qsl(" (") + url() + ')'; + } else if (mode == ExpandLinksUrlOnly) { + result = url(); } return result; } TextWithEntities HiddenUrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const { TextWithEntities result; - result.entities.push_back({ EntityInTextCustomUrl, entityOffset, textPart.size(), url() }); - if (mode == ExpandLinksAll) { - result.text = textPart.toString() + qsl(" (") + url() + ')'; + if (mode == ExpandLinksUrlOnly) { + result.text = url(); + result.entities.push_back({ EntityInTextUrl, entityOffset, result.text.size() }); + } else { + result.entities.push_back({ EntityInTextCustomUrl, entityOffset, textPart.size(), url() }); + if (mode == ExpandLinksAll) { + result.text = textPart.toString() + qsl(" (") + url() + ')'; + } } return result; } @@ -174,9 +181,19 @@ TextWithEntities HashtagClickHandler::getExpandedLinkTextWithEntities(ExpandLink return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() }); } +PeerData *BotCommandClickHandler::_peer = nullptr; +UserData *BotCommandClickHandler::_bot = nullptr; void BotCommandClickHandler::onClick(Qt::MouseButton button) const { if (button == Qt::LeftButton || button == Qt::MiddleButton) { - if (PeerData *peer = Ui::getPeerForMouseAction()) { + if (auto peer = peerForCommand()) { + if (auto bot = peer->isUser() ? peer->asUser() : botForCommand()) { + Ui::showPeerHistory(peer, ShowAtTheEndMsgId); + App::sendBotCommand(peer, bot, _cmd); + return; + } + } + + if (auto peer = Ui::getPeerForMouseAction()) { // old way UserData *bot = peer->isUser() ? peer->asUser() : nullptr; if (auto item = App::hoveredLinkItem()) { if (!bot) { diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index d25078278..84deafe77 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -193,6 +193,8 @@ private: }; +class PeerData; +class UserData; class BotCommandClickHandler : public TextClickHandler { public: BotCommandClickHandler(const QString &cmd) : _cmd(cmd) { @@ -204,14 +206,30 @@ public: return _cmd; } + static void setPeerForCommand(PeerData *peer) { + _peer = peer; + } + static void setBotForCommand(UserData *bot) { + _bot = bot; + } + TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override; protected: QString url() const override { return _cmd; } + static PeerData *peerForCommand() { + return _peer; + } + static UserData *botForCommand() { + return _bot; + } private: QString _cmd; + static PeerData *_peer; + static UserData *_bot; + }; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 4375ede39..3e41bac0a 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -5034,7 +5034,7 @@ void HistoryWidget::onBroadcastSilentChange() { } void HistoryWidget::onShareContact(const PeerId &peer, UserData *contact) { - auto phone = contact->phone; + auto phone = contact->phone(); if (phone.isEmpty()) phone = App::phoneFromSharedContact(peerToUser(contact->id)); if (!contact || phone.isEmpty()) return; diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp index e5fcb4956..48cc83f8b 100644 --- a/Telegram/SourceFiles/intro/introphone.cpp +++ b/Telegram/SourceFiles/intro/introphone.cpp @@ -50,7 +50,7 @@ IntroPhone::IntroPhone(IntroWidget *parent) : IntroStep(parent) , country(this, st::introCountry) , phone(this, st::inpIntroPhone) , code(this, st::inpIntroCountryCode) -, _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), st::introErrLabel, st::introErrLabelTextStyle) +, _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), FlatLabel::InitType::Rich, st::introErrLabel, st::introErrLabelTextStyle) , _showSignup(false) , sentRequest(0) { setVisible(false); diff --git a/Telegram/SourceFiles/intro/introstart.cpp b/Telegram/SourceFiles/intro/introstart.cpp index 8753300fc..bc3c520ff 100644 --- a/Telegram/SourceFiles/intro/introstart.cpp +++ b/Telegram/SourceFiles/intro/introstart.cpp @@ -27,7 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "langloaderplain.h" IntroStart::IntroStart(IntroWidget *parent) : IntroStep(parent) -, _intro(this, lang(lng_intro), st::introLabel, st::introLabelTextStyle) +, _intro(this, lang(lng_intro), FlatLabel::InitType::Rich, st::introLabel, st::introLabelTextStyle) , _changeLang(this, QString()) , _next(this, lang(lng_start_msgs), st::btnIntroNext) { _changeLang.hide(); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index a94231f4a..9127b6abb 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -3407,7 +3407,7 @@ namespace Local { UserData *user = peer->asUser(); // first + last + phone + username + access - result += Serialize::stringSize(user->firstName) + Serialize::stringSize(user->lastName) + Serialize::stringSize(user->phone) + Serialize::stringSize(user->username) + sizeof(quint64); + result += Serialize::stringSize(user->firstName) + Serialize::stringSize(user->lastName) + Serialize::stringSize(user->phone()) + Serialize::stringSize(user->username) + sizeof(quint64); // flags if (AppVersion >= 9012) { @@ -3436,7 +3436,7 @@ namespace Local { if (peer->isUser()) { UserData *user = peer->asUser(); - stream << user->firstName << user->lastName << user->phone << user->username << quint64(user->access); + stream << user->firstName << user->lastName << user->phone() << user->username << quint64(user->access); if (AppVersion >= 9012) { stream << qint32(user->flags); } @@ -3490,6 +3490,7 @@ namespace Local { QString pname = (showPhone && !phone.isEmpty()) ? App::formatPhone(phone) : QString(); if (!wasLoaded) { + user->setPhone(phone); user->setNameDelayed(first, last, pname, username); user->access = access; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 3a60bfb44..b3c12ede1 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -4389,9 +4389,16 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { case mtpc_updateUserPhone: { auto &d(update.c_updateUserPhone()); if (auto user = App::userLoaded(d.vuser_id.v)) { - user->setPhone(qs(d.vphone)); - user->setNameDelayed(user->firstName, user->lastName, (user->contact || isServiceUser(user->id) || user->isSelf() || user->phone.isEmpty()) ? QString() : App::formatPhone(user->phone), user->username); - App::markPeerUpdated(user); + auto newPhone = qs(d.vphone); + if (newPhone != user->phone()) { + user->setPhone(newPhone); + user->setNameDelayed(user->firstName, user->lastName, (user->contact || isServiceUser(user->id) || user->isSelf() || user->phone().isEmpty()) ? QString() : App::formatPhone(user->phone()), user->username); + App::markPeerUpdated(user); + + Notify::PeerUpdate update(user); + update.flags |= Notify::PeerUpdateFlag::UserPhoneChanged; + Notify::peerUpdatedDelayed(update); + } } } break; diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h index 06f07b73a..797dd562d 100644 --- a/Telegram/SourceFiles/observer_peer.h +++ b/Telegram/SourceFiles/observer_peer.h @@ -33,9 +33,11 @@ enum class PeerUpdateFlag { NameChanged = 0x00000001U, UsernameChanged = 0x00000002U, PhotoChanged = 0x00000004U, + AboutChanged = 0x00000008U, UserCanShareContact = 0x00010000U, UserIsContact = 0x00020000U, + UserPhoneChanged = 0x00040000U, ChatCanEdit = 0x00010000U, diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index 76fc87208..cd0577934 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -37,7 +37,8 @@ profileFixedBarButton: flatButton(topBarButton) { profileMarginTop: 13px; profilePhotoSize: 112px; -profilePhotoLeft: 35px; +profilePhotoLeftMin: 18px; +profilePhotoLeftMax: 45px; profilePhotoDuration: 500; profileNameLeft: 26px; profileNameTop: 9px; @@ -106,7 +107,28 @@ profileDividerFill: icon { }; profileBlocksTop: 7px; +profileBlocksBottom: 20px; +profileBlockLeftMin: 8px; +profileBlockLeftMax: 25px; +profileBlockNarrowWidthMin: 220px; +profileBlockWideWidthMin: 300px; +profileBlockWideWidthMax: 340px; profileBlockMarginTop: 21px; -profileBlockTitleFont: semiboldFont; +profileBlockMarginRight: 10px; +profileBlockMarginBottom: 4px; +profileBlockTitleHeight: 22px; +profileBlockTitleFont: font(14px semibold); profileBlockTitleFg: black; -profileBlockTitlePosition: point(16px, profileBlockMarginTop); \ No newline at end of file +profileBlockTitlePosition: point(24px, -7px); +profileBlockLabel: flatLabel(labelDefFlat) { + textFg: windowSubTextFg; +} +profileBlockTextPart: flatLabel(labelDefFlat) { + width: 180px; + margin: margins(5px, 5px, 5px, 5px); +} +profileBlockOneLineTextPart: flatLabel(profileBlockTextPart) { + width: 0px; // No need to set minWidth in one-line text. + maxHeight: 20px; +} +profileBlockOneLineSkip: 9px; diff --git a/Telegram/SourceFiles/profile/profile_actions_widget.cpp b/Telegram/SourceFiles/profile/profile_actions_widget.cpp new file mode 100644 index 000000000..aec40e5c8 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_actions_widget.cpp @@ -0,0 +1,40 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "profile/profile_actions_widget.h" + +#include "styles/style_profile.h" +#include "lang.h" + +namespace Profile { + +ActionsWidget::ActionsWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_actions_section)) +{ + show(); +} + +int ActionsWidget::resizeGetHeight(int newWidth) { + int newHeight = contentTop(); + + return newHeight; +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_actions_widget.h b/Telegram/SourceFiles/profile/profile_actions_widget.h new file mode 100644 index 000000000..118c2d0d5 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_actions_widget.h @@ -0,0 +1,37 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "profile/profile_block_widget.h" + +namespace Profile { + +class ActionsWidget : public BlockWidget { +public: + ActionsWidget(QWidget *parent, PeerData *peer); + +protected: + // Resizes content and counts natural widget height for the desired width. + int resizeGetHeight(int newWidth) override; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_block_widget.cpp b/Telegram/SourceFiles/profile/profile_block_widget.cpp index abfe8453b..6548fbb61 100644 --- a/Telegram/SourceFiles/profile/profile_block_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_block_widget.cpp @@ -34,12 +34,18 @@ void BlockWidget::resizeToWidth(int newWidth) { resize(newWidth, resizeGetHeight(newWidth)); } +int BlockWidget::contentTop() const { + return st::profileBlockMarginTop + st::profileBlockTitleHeight; +} + void BlockWidget::paintEvent(QPaintEvent *e) { Painter p(this); p.setFont(st::profileBlockTitleFont); p.setPen(st::profileBlockTitleFg); - p.drawText(st::profileBlockTitlePosition, _title); + int titleLeft = st::profileBlockTitlePosition.x(); + int titleTop = st::profileBlockMarginTop + st::profileBlockTitlePosition.y(); + p.drawTextLeft(titleLeft, titleTop, width(), _title); paintContents(p); } diff --git a/Telegram/SourceFiles/profile/profile_block_widget.h b/Telegram/SourceFiles/profile/profile_block_widget.h index 313e0d747..8607aa0b5 100644 --- a/Telegram/SourceFiles/profile/profile_block_widget.h +++ b/Telegram/SourceFiles/profile/profile_block_widget.h @@ -35,14 +35,25 @@ public: virtual void setVisibleTopBottom(int visibleTop, int visibleBottom) { } +signals: + void heightUpdated(); + protected: void paintEvent(QPaintEvent *e) override; virtual void paintContents(Painter &p) { } + // Where does the block content start (after the title). + int contentTop() const; + // Resizes content and counts natural widget height for the desired width. virtual int resizeGetHeight(int newWidth) = 0; + void contentSizeUpdated() { + resizeToWidth(width()); + emit heightUpdated(); + } + PeerData *peer() const { return _peer; } diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index fd8937987..8ed491b80 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -80,7 +80,7 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _peerChannel(peer->asChannel()) , _peerMegagroup(peer->isMegagroup() ? _peerChannel : nullptr) , _userpicButton(this, peer) -, _name(this, QString(), st::profileNameLabel) { +, _name(this, st::profileNameLabel) { setAttribute(Qt::WA_OpaquePaintEvent); setAcceptDrops(true); @@ -109,20 +109,23 @@ void CoverWidget::onPhotoShow() { } } +int CoverWidget::countPhotoLeft(int newWidth) const { + int result = st::profilePhotoLeftMin; + result += (newWidth - st::wndMinWidth) / 2; + return qMin(result, st::profilePhotoLeftMax); +} + void CoverWidget::resizeToWidth(int newWidth) { int newHeight = 0; newHeight += st::profileMarginTop; - _userpicButton->moveToLeft(st::profilePhotoLeft, newHeight); + + _photoLeft = countPhotoLeft(newWidth); + _userpicButton->moveToLeft(_photoLeft, newHeight); + + refreshNameGeometry(newWidth); int infoLeft = _userpicButton->x() + _userpicButton->width(); - int nameLeft = infoLeft + st::profileNameLeft - st::profileNameLabel.margin.left(); - int nameTop = _userpicButton->y() + st::profileNameTop - st::profileNameLabel.margin.top(); - _name.moveToLeft(nameLeft, nameTop); - int nameWidth = newWidth - infoLeft - st::profileNameLeft - st::profileButtonSkip; - nameWidth += st::profileNameLabel.margin.left() + st::profileNameLabel.margin.right(); - _name.resizeToWidth(nameWidth); - _statusPosition = QPoint(infoLeft + st::profileStatusLeft, _userpicButton->y() + st::profileStatusTop); moveAndToggleButtons(newWidth); @@ -140,29 +143,43 @@ void CoverWidget::resizeToWidth(int newWidth) { update(); } +void CoverWidget::refreshNameGeometry(int newWidth) { + int infoLeft = _userpicButton->x() + _userpicButton->width(); + int nameLeft = infoLeft + st::profileNameLeft - st::profileNameLabel.margin.left(); + int nameTop = _userpicButton->y() + st::profileNameTop - st::profileNameLabel.margin.top(); + int nameWidth = newWidth - infoLeft - st::profileNameLeft - st::profileButtonSkip; + int marginsAdd = st::profileNameLabel.margin.left() + st::profileNameLabel.margin.right(); + _name.resizeToWidth(qMin(nameWidth, _name.naturalWidth()) + marginsAdd); + _name.moveToLeft(nameLeft, nameTop); +} + // A more generic solution would be allowing an optional icon button // for each text button. But currently we use only one, so it is done easily: // There can be primary + secondary + icon buttons. If primary + secondary fit, // then icon is hidden, otherwise secondary is hidden and icon is shown. void CoverWidget::moveAndToggleButtons(int newWiddth) { - bool showNextButton = true; - int buttonLeft = st::profilePhotoLeft + _userpicButton->width() + st::profileButtonLeft; + int buttonLeft = _userpicButton->x() + _userpicButton->width() + st::profileButtonLeft; int buttonsRight = newWiddth - st::profileButtonSkip; for (int i = 0, count = _buttons.size(); i < count; ++i) { - auto button = _buttons.at(i); - button->moveToLeft(buttonLeft, st::profileButtonTop); - if (i == 1) { - // If second button is not fitting. - if (buttonLeft + button->width() > buttonsRight) { - button->hide(); + auto &button = _buttons.at(i); + button.widget->moveToLeft(buttonLeft, st::profileButtonTop); + if (button.replacement) { + button.replacement->moveToLeft(buttonLeft, st::profileButtonTop); + if (buttonLeft + button.widget->width() > buttonsRight) { + button.widget->hide(); + button.replacement->show(); + buttonLeft += button.replacement->width() + st::profileButtonSkip; } else { - button->show(); - buttonLeft += button->width() + st::profileButtonSkip; - showNextButton = false; + button.widget->show(); + button.replacement->hide(); + buttonLeft += button.widget->width() + st::profileButtonSkip; } + } else if (i == 1 && (buttonLeft + button.widget->width() > buttonsRight)) { + // If second button is not fitting. + button.widget->hide(); } else { - button->setVisible(showNextButton); - buttonLeft += button->width() + st::profileButtonSkip; + button.widget->show(); + buttonLeft += button.widget->width() + st::profileButtonSkip; } } } @@ -172,7 +189,7 @@ void CoverWidget::showFinished() { } bool CoverWidget::shareContactButtonShown() const { - return _peerUser && (_buttons.size() > 1) && !(_buttons.at(1)->isHidden()); + return _peerUser && (_buttons.size() > 1) && !(_buttons.at(1).widget->isHidden()); } void CoverWidget::paintEvent(QPaintEvent *e) { @@ -195,8 +212,7 @@ void CoverWidget::resizeDropArea() { void CoverWidget::dropAreaHidden(CoverDropArea *dropArea) { if (_dropArea == dropArea) { - _dropArea->deleteLater(); - _dropArea = nullptr; + _dropArea.destroyDelayed(); } } @@ -314,7 +330,7 @@ void CoverWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { void CoverWidget::refreshNameText() { _name.setText(App::peerName(_peer)); - update(); + refreshNameGeometry(width()); } void CoverWidget::refreshStatusText() { @@ -394,8 +410,7 @@ void CoverWidget::setUserButtons() { void CoverWidget::setChatButtons() { if (_peerChat->canEdit()) { addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); - addButton(lang(lng_profile_add_participant), SLOT(onAddMember())); - addButton(st::profileAddMemberButton, SLOT(onAddMember())); + addButton(lang(lng_profile_add_participant), SLOT(onAddMember()), &st::profileAddMemberButton); } } @@ -404,8 +419,7 @@ void CoverWidget::setMegagroupButtons() { addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); } if (_peerMegagroup->canAddParticipants()) { - addButton(lang(lng_profile_add_participant), SLOT(onAddMember())); - addButton(st::profileAddMemberButton, SLOT(onAddMember())); + addButton(lang(lng_profile_add_participant), SLOT(onAddMember()), &st::profileAddMemberButton); } } @@ -422,21 +436,24 @@ void CoverWidget::setChannelButtons() { void CoverWidget::clearButtons() { auto buttons = createAndSwap(_buttons); for_const (auto button, buttons) { - delete button; + delete button.widget; + delete button.replacement; } } -void CoverWidget::addButton(const QString &text, const char *slot) { +void CoverWidget::addButton(const QString &text, const char *slot, const style::BoxButton *replacementStyle) { auto &buttonStyle = _buttons.isEmpty() ? st::profilePrimaryButton : st::profileSecondaryButton; - _buttons.push_back(new Ui::RoundButton(this, text, buttonStyle)); - connect(_buttons.back(), SIGNAL(clicked()), this, slot); - _buttons.back()->show(); -} + auto button = new Ui::RoundButton(this, text, buttonStyle); + connect(button, SIGNAL(clicked()), this, slot); + button->show(); -void CoverWidget::addButton(const style::BoxButton &buttonStyle, const char *slot) { - _buttons.push_back(new Ui::RoundButton(this, QString(), buttonStyle)); - connect(_buttons.back(), SIGNAL(clicked()), this, slot); - _buttons.back()->hide(); + auto replacement = replacementStyle ? new Ui::RoundButton(this, QString(), *replacementStyle) : nullptr; + if (replacement) { + connect(replacement, SIGNAL(clicked()), this, slot); + replacement->hide(); + } + + _buttons.push_back({ button, replacement }); } void CoverWidget::onSendMessage() { diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index 47975485b..2a53e31ec 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -75,6 +75,10 @@ private: void notifyPeerUpdated(const Notify::PeerUpdate &update); void notifyFileQueryUpdated(const FileDialog::QueryUpdate &update); + // Counts userpic button left offset for a new widget width. + int countPhotoLeft(int newWidth) const; + + void refreshNameGeometry(int newWidth); void moveAndToggleButtons(int newWiddth); void refreshNameText(); void refreshStatusText(); @@ -87,8 +91,7 @@ private: void setChannelButtons(); void clearButtons(); - void addButton(const QString &text, const char *slot); - void addButton(const style::BoxButton &buttonStyle, const char *slot); + void addButton(const QString &text, const char *slot, const style::BoxButton *replacementStyle = nullptr); void paintDivider(Painter &p); @@ -112,8 +115,13 @@ private: QPoint _statusPosition; QString _statusText; - QList<Ui::RoundButton*> _buttons; + struct Button { + Ui::RoundButton *widget; + Ui::RoundButton *replacement; + }; + QList<Button> _buttons; + int _photoLeft = 0; // Caching countPhotoLeft() result. int _dividerTop = 0; FileDialog::QueryId _setPhotoFileQueryId = 0; diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp index 71980895f..e051bc8f3 100644 --- a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp @@ -177,7 +177,7 @@ void FixedBar::onEditGroup() { void FixedBar::onAddContact() { auto firstName = _peerUser->firstName; auto lastName = _peerUser->lastName; - auto phone = _peerUser->phone.isEmpty() ? App::phoneFromSharedContact(peerToUser(_peer->id)) : _peerUser->phone; + auto phone = _peerUser->phone().isEmpty() ? App::phoneFromSharedContact(peerToUser(_peer->id)) : _peerUser->phone(); Ui::showLayer(new AddContactBox(firstName, lastName, phone)); } @@ -223,8 +223,8 @@ void FixedBar::resizeToWidth(int newWidth) { i->button->moveToLeft(buttonLeft, 0); } - _backButton->moveToLeft(0, 0); _backButton->resizeToWidth(newWidth); + _backButton->moveToLeft(0, 0); newHeight += _backButton->height(); resize(newWidth, newHeight); diff --git a/Telegram/SourceFiles/profile/profile_info_widget.cpp b/Telegram/SourceFiles/profile/profile_info_widget.cpp new file mode 100644 index 000000000..04ec57bc0 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_info_widget.cpp @@ -0,0 +1,217 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "profile/profile_info_widget.h" + +#include "styles/style_profile.h" +#include "ui/flatlabel.h" +#include "core/click_handler_types.h" +#include "observer_peer.h" +#include "lang.h" + +namespace Profile { + +InfoWidget::InfoWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_info_section)) { + auto observeEvents = Notify::PeerUpdateFlag::AboutChanged + | Notify::PeerUpdateFlag::UsernameChanged + | Notify::PeerUpdateFlag::UserPhoneChanged + | Notify::PeerUpdateFlag::UserCanShareContact; + Notify::registerPeerObserver(observeEvents, this, &InfoWidget::notifyPeerUpdated); + + refreshLabels(); +} + +void InfoWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.peer != peer()) { + return; + } + + if (update.flags & Notify::PeerUpdateFlag::AboutChanged) { + refreshAbout(); + } + if (update.flags & Notify::PeerUpdateFlag::UsernameChanged) { + refreshUsername(); + refreshChannelLink(); + } + if (update.flags & (Notify::PeerUpdateFlag::UserPhoneChanged | Notify::PeerUpdateFlag::UserCanShareContact)) { + refreshMobileNumber(); + } + refreshVisibility(); + + contentSizeUpdated(); +} + +int InfoWidget::resizeGetHeight(int newWidth) { + int newHeight = contentTop(); + + int marginLeft = st::profileBlockTextPart.margin.left(); + int marginRight = st::profileBlockTextPart.margin.right(); + int left = st::profileBlockTitlePosition.x(); + if (_about) { + int textWidth = _about->naturalWidth(); + int availableWidth = newWidth - left - st::profileBlockMarginRight; + int maxWidth = st::msgMaxWidth; + accumulate_min(textWidth, availableWidth); + accumulate_min(textWidth, st::msgMaxWidth); + _about->resizeToWidth(textWidth + marginLeft + marginRight); + _about->moveToLeft(left - marginLeft, newHeight - st::profileBlockTextPart.margin.top()); + newHeight += _about->height(); + } + + auto moveLabeledText = [&newHeight, left, newWidth, marginLeft, marginRight](FlatLabel *label, FlatLabel *text, FlatLabel *shortText) { + if (!label) return; + + label->moveToLeft(left, newHeight); + int textLeft = left + label->width() + st::normalFont->spacew; + int textWidth = text->naturalWidth(); + int availableWidth = newWidth - textLeft - st::profileBlockMarginRight; + bool doesNotFit = (textWidth > availableWidth); + accumulate_min(textWidth, availableWidth); + accumulate_min(textWidth, st::msgMaxWidth); + text->resizeToWidth(textWidth + marginLeft + marginRight); + text->moveToLeft(textLeft - marginLeft, newHeight - st::profileBlockOneLineTextPart.margin.top()); + if (shortText) { + shortText->resizeToWidth(textWidth + marginLeft + marginRight); + shortText->moveToLeft(textLeft - marginLeft, newHeight - st::profileBlockOneLineTextPart.margin.top()); + if (doesNotFit) { + shortText->show(); + text->hide(); + } else { + shortText->hide(); + text->show(); + } + } + newHeight += label->height() + st::profileBlockOneLineSkip; + }; + moveLabeledText(_channelLinkLabel, _channelLink, _channelLinkShort); + moveLabeledText(_mobileNumberLabel, _mobileNumber, nullptr); + moveLabeledText(_usernameLabel, _username, nullptr); + + newHeight += st::profileBlockMarginBottom; + return newHeight; +} + +void InfoWidget::leaveEvent(QEvent *e) { + BotCommandClickHandler::setPeerForCommand(nullptr); + BotCommandClickHandler::setBotForCommand(nullptr); +} + +void InfoWidget::refreshLabels() { + refreshAbout(); + refreshMobileNumber(); + refreshUsername(); + refreshChannelLink(); + + refreshVisibility(); +} + +void InfoWidget::refreshVisibility() { + setVisible(_about || _mobileNumber || _username || _channelLink); +} + +void InfoWidget::refreshAbout() { + auto getAboutText = [this]() -> QString { + if (auto user = peer()->asUser()) { + return user->about(); + } else if (auto channel = peer()->asChannel()) { + return channel->about(); + } + return QString(); + }; + + _about.destroy(); + auto aboutText = getAboutText(); + if (!aboutText.isEmpty()) { + _about = new FlatLabel(this, QString(), FlatLabel::InitType::Simple, st::profileBlockTextPart); + _about->show(); + + EntitiesInText aboutEntities; + textParseEntities(aboutText, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands, &aboutEntities); + _about->setMarkedText({ aboutText, aboutEntities }); + _about->setSelectable(true); + _about->setClickHandlerHook(func(this, &InfoWidget::aboutClickHandlerHook)); + } +} + +bool InfoWidget::aboutClickHandlerHook(const ClickHandlerPtr &handler, Qt::MouseButton button) { + BotCommandClickHandler::setPeerForCommand(peer()); + return true; +} + +void InfoWidget::refreshMobileNumber() { + TextWithEntities phoneText; + if (auto user = peer()->asUser()) { + if (!user->phone().isEmpty()) { + phoneText.text = App::formatPhone(user->phone()); + } else { + phoneText.text = App::phoneFromSharedContact(peerToUser(user->id)); + } + } + setLabeledText(&_mobileNumberLabel, lang(lng_profile_mobile_number), &_mobileNumber, phoneText, lang(lng_profile_copy_phone)); +} + +void InfoWidget::refreshUsername() { + TextWithEntities usernameText; + if (auto user = peer()->asUser()) { + if (!user->username.isEmpty()) { + usernameText.text = '@' + user->username; + } + } + setLabeledText(&_usernameLabel, lang(lng_profile_username), &_username, usernameText, lang(lng_context_copy_mention)); +} + +void InfoWidget::refreshChannelLink() { + TextWithEntities channelLinkText; + TextWithEntities channelLinkTextShort; + if (auto channel = peer()->asChannel()) { + if (!channel->username.isEmpty()) { + channelLinkText.text = qsl("https://telegram.me/") + channel->username; + channelLinkText.entities.push_back(EntityInText(EntityInTextUrl, 0, channelLinkText.text.size())); + channelLinkTextShort.text = qsl("telegram.me/") + channel->username; + channelLinkTextShort.entities.push_back(EntityInText(EntityInTextCustomUrl, 0, channelLinkTextShort.text.size(), qsl("https://telegram.me/") + channel->username)); + } + } + setLabeledText(nullptr, lang(lng_profile_link), &_channelLink, channelLinkText, QString()); + setLabeledText(&_channelLinkLabel, lang(lng_profile_link), &_channelLinkShort, channelLinkTextShort, QString()); + if (_channelLinkShort) { + _channelLinkShort->setExpandLinksMode(ExpandLinksUrlOnly); + } +} + +void InfoWidget::setLabeledText(ChildWidget<FlatLabel> *labelWidget, const QString &label, + ChildWidget<FlatLabel> *textWidget, const TextWithEntities &textWithEntities, const QString ©Text) { + if (labelWidget) labelWidget->destroy(); + textWidget->destroy(); + if (textWithEntities.text.isEmpty()) return; + + if (labelWidget) { + *labelWidget = new FlatLabel(this, label, FlatLabel::InitType::Simple, st::profileBlockLabel); + (*labelWidget)->show(); + } + *textWidget = new FlatLabel(this, QString(), FlatLabel::InitType::Simple, st::profileBlockOneLineTextPart); + (*textWidget)->show(); + (*textWidget)->setMarkedText(textWithEntities); + (*textWidget)->setContextCopyText(copyText); + (*textWidget)->setSelectable(true); + (*textWidget)->setDoubleClickSelectsParagraph(true); +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_info_widget.h b/Telegram/SourceFiles/profile/profile_info_widget.h new file mode 100644 index 000000000..b94f5199d --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_info_widget.h @@ -0,0 +1,72 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "profile/profile_block_widget.h" +#include "core/observer.h" + +class FlatLabel; + +namespace Notify { +struct PeerUpdate; +} // namespace Notify + +namespace Profile { + +class InfoWidget : public BlockWidget, public Notify::Observer { +public: + InfoWidget(QWidget *parent, PeerData *peer); + +protected: + // Resizes content and counts natural widget height for the desired width. + int resizeGetHeight(int newWidth) override; + + void leaveEvent(QEvent *e) override; + +private: + // Observed notifications. + void notifyPeerUpdated(const Notify::PeerUpdate &update); + + void refreshLabels(); + void refreshAbout(); + void refreshMobileNumber(); + void refreshUsername(); + void refreshChannelLink(); + void refreshVisibility(); + + bool aboutClickHandlerHook(const ClickHandlerPtr &handler, Qt::MouseButton button); + + // labelWidget may be nullptr. + void setLabeledText(ChildWidget<FlatLabel> *labelWidget, const QString &label, + ChildWidget<FlatLabel> *textWidget, const TextWithEntities &textWithEntities, const QString ©Text); + + ChildWidget<FlatLabel> _about = { nullptr }; + ChildWidget<FlatLabel> _channelLinkLabel = { nullptr }; + ChildWidget<FlatLabel> _channelLink = { nullptr }; + ChildWidget<FlatLabel> _channelLinkShort = { nullptr }; + ChildWidget<FlatLabel> _mobileNumberLabel = { nullptr }; + ChildWidget<FlatLabel> _mobileNumber = { nullptr }; + ChildWidget<FlatLabel> _usernameLabel = { nullptr }; + ChildWidget<FlatLabel> _username = { nullptr }; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.cpp b/Telegram/SourceFiles/profile/profile_inner_widget.cpp index 77e701ac9..0cd9d0e17 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_inner_widget.cpp @@ -23,6 +23,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_profile.h" #include "profile/profile_cover.h" +#include "profile/profile_info_widget.h" +#include "profile/profile_settings_widget.h" +#include "profile/profile_invite_link_widget.h" +#include "profile/profile_shared_media_widget.h" +#include "profile/profile_actions_widget.h" +#include "profile/profile_members_widget.h" #include "apiwrap.h" namespace Profile { @@ -31,6 +37,33 @@ InnerWidget::InnerWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _peer(peer) , _cover(this, peer) { setAttribute(Qt::WA_OpaquePaintEvent); + + createBlocks(); +} + +void InnerWidget::createBlocks() { + auto user = _peer->asUser(); + auto chat = _peer->asChat(); + auto channel = _peer->asChannel(); + auto megagroup = _peer->isMegagroup() ? channel : nullptr; + if (user || channel || megagroup) { + _blocks.push_back({ new InfoWidget(this, _peer), BlockSide::Right }); + } + _blocks.push_back({ new SettingsWidget(this, _peer), BlockSide::Right }); + if (chat || channel || megagroup) { + _blocks.push_back({ new InviteLinkWidget(this, _peer), BlockSide::Right }); + } + _blocks.push_back({ new SharedMediaWidget(this, _peer), BlockSide::Right }); + _blocks.push_back({ new ActionsWidget(this, _peer), BlockSide::Right }); + if (channel && !megagroup) { + _blocks.push_back({ new ChannelMembersWidget(this, _peer), BlockSide::Right }); + } + if (chat || megagroup) { + _blocks.push_back({ new MembersWidget(this, _peer), BlockSide::Left }); + } + for_const (auto &blockData, _blocks) { + connect(blockData.block, SIGNAL(heightUpdated()), this, SLOT(onBlockHeightUpdated())); + } } void InnerWidget::resizeToWidth(int newWidth, int minHeight) { @@ -45,7 +78,7 @@ void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { int notDisplayedAtBottom = height() - _visibleBottom; if (notDisplayedAtBottom > 0) { -// decreaseAdditionalHeight(notDisplayedAtBottom); // testing + decreaseAdditionalHeight(notDisplayedAtBottom); } //loadProfilePhotos(_visibleTop); @@ -72,6 +105,16 @@ void InnerWidget::paintEvent(QPaintEvent *e) { Painter p(this); p.fillRect(e->rect(), st::profileBg); + + if (_mode == Mode::TwoColumn) { + int leftHeight = countBlocksHeight(BlockSide::Left); + int rightHeight = countBlocksHeight(BlockSide::Right); + int minHeight = qMin(leftHeight, rightHeight); + + int shadowLeft = _blocksLeft + _leftColumnWidth + _columnDivider; + int shadowTop = _blocksTop + st::profileBlockMarginTop; + p.fillRect(rtlrect(shadowLeft, shadowTop, st::lineWidth, minHeight - st::profileBlockMarginTop, width()), st::shadowColor); + } } void InnerWidget::keyPressEvent(QKeyEvent *e) { @@ -80,11 +123,134 @@ void InnerWidget::keyPressEvent(QKeyEvent *e) { } } +int InnerWidget::countBlocksHeight(BlockSide countSide) const { + int result = 0; + for_const (auto &blockData, _blocks) { + if (blockData.side != countSide || blockData.block->isHidden()) { + continue; + } + + result += blockData.block->height(); + } + return result; +} + +int InnerWidget::countBlocksLeft(int newWidth) const { + int result = st::profileBlockLeftMin; + result += (newWidth - st::wndMinWidth) / 2; + return qMin(result, st::profileBlockLeftMax); +} + +InnerWidget::Mode InnerWidget::countBlocksMode(int newWidth) const { + bool hasLeftWidget = false, hasRightWidget = false; + for_const (auto &blockData, _blocks) { + if (!blockData.block->isHidden()) { + if (blockData.side == BlockSide::Left) { + hasLeftWidget = true; + } else { + hasRightWidget = true; + } + } + } + if (!hasLeftWidget || !hasRightWidget) { + return Mode::OneColumn; + } + + int availWidth = newWidth - _blocksLeft; + if (availWidth >= st::profileBlockWideWidthMin + _columnDivider + st::profileBlockNarrowWidthMin) { + return Mode::TwoColumn; + } + return Mode::OneColumn; +} + +int InnerWidget::countLeftColumnWidth(int newWidth) const { + int result = st::profileBlockWideWidthMin; + + int availWidth = newWidth - _blocksLeft; + int additionalWidth = (availWidth - st::profileBlockWideWidthMin - _columnDivider - st::profileBlockNarrowWidthMin); + if (additionalWidth > 0) { + result += (additionalWidth / 2); + accumulate_min(result, st::profileBlockWideWidthMax); + } + return result; +} + +void InnerWidget::refreshBlocksPositions() { + auto layoutBlocks = [this](BlockSide layoutSide, int left) { + int top = _blocksTop; + for_const (auto &blockData, _blocks) { + if (_mode == Mode::TwoColumn && blockData.side != layoutSide) { + continue; + } + if (blockData.block->isHidden()) { + continue; + } + blockData.block->moveToLeft(left, top); + top += blockData.block->height(); + } + }; + layoutBlocks(BlockSide::Left, _blocksLeft); + if (_mode == Mode::TwoColumn) { + layoutBlocks(BlockSide::Right, _blocksLeft + _leftColumnWidth + _columnDivider); + } +} + +void InnerWidget::resizeBlocks(int newWidth) { + for_const (auto &blockData, _blocks) { + int blockWidth = newWidth - _blocksLeft; + if (_mode == Mode::OneColumn) { + blockWidth -= _blocksLeft; + } else { + if (blockData.side == BlockSide::Left) { + blockWidth = _leftColumnWidth; + } else { + blockWidth -= _leftColumnWidth + _columnDivider; + } + } + blockData.block->resizeToWidth(blockWidth); + } +} + int InnerWidget::resizeGetHeight(int newWidth) { _cover->resizeToWidth(newWidth); + + _blocksTop = _cover->y() + _cover->height() + st::profileBlocksTop; + _blocksLeft = countBlocksLeft(newWidth); + _columnDivider = _blocksLeft; + _mode = countBlocksMode(newWidth); + _leftColumnWidth = countLeftColumnWidth(newWidth); + resizeBlocks(newWidth); + + refreshBlocksPositions(); + + update(); + return countHeight(); +} + +int InnerWidget::countHeight() const { int newHeight = _cover->height(); + int leftHeight = countBlocksHeight(BlockSide::Left); + int rightHeight = countBlocksHeight(BlockSide::Right); + + int blocksHeight = (_mode == Mode::OneColumn) ? (leftHeight + rightHeight) : qMax(leftHeight, rightHeight); + newHeight += st::profileBlocksTop + blocksHeight + st::profileBlocksBottom; return newHeight; } +void InnerWidget::onBlockHeightUpdated() { + refreshBlocksPositions(); + + int naturalHeight = countHeight(); + int notDisplayedAtBottom = naturalHeight - _visibleBottom; + if (notDisplayedAtBottom < 0) { + _addedHeight = -notDisplayedAtBottom; + } else { + _addedHeight = 0; + } + if (naturalHeight + _addedHeight != height()) { + resize(width(), naturalHeight + _addedHeight); + } +} + } // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.h b/Telegram/SourceFiles/profile/profile_inner_widget.h index e702b17cd..61467d55b 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.h +++ b/Telegram/SourceFiles/profile/profile_inner_widget.h @@ -51,14 +51,37 @@ public: signals: void cancelled(); +private slots: + void onBlockHeightUpdated(); + protected: void paintEvent(QPaintEvent *e) override; void keyPressEvent(QKeyEvent *e) override; private: + void createBlocks(); + // Resizes content and counts natural widget height for the desired width. int resizeGetHeight(int newWidth); + // Counts the natural widget height after resizing of child widgets. + int countHeight() const; + + enum class Mode { + OneColumn, + TwoColumn, + }; + enum class BlockSide { + Left, + Right, + }; + int countBlocksLeft(int newWidth) const; + Mode countBlocksMode(int newWidth) const; + int countLeftColumnWidth(int newWidth) const; + int countBlocksHeight(BlockSide countSide) const; + void resizeBlocks(int newWidth); + void refreshBlocksPositions(); + // Sometimes height of this widget is larger than it is required // so that it is allowed to scroll down to the desired position. // When resizing with scroll moving up the additional height may be decreased. @@ -73,8 +96,18 @@ private: int _visibleBottom = 0; ChildWidget<CoverWidget> _cover; - QList<BlockWidget*> _blocks; + int _blocksLeft = 0; // Caching countBlocksLeft() result. + int _blocksTop = 0; + int _columnDivider = 0; + int _leftColumnWidth = 0; // Caching countLeftColumnWidth() result. + struct Block { + BlockWidget *block; + BlockSide side; + }; + QList<Block> _blocks; + + Mode _mode = Mode::OneColumn; }; } // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_invite_link_widget.cpp b/Telegram/SourceFiles/profile/profile_invite_link_widget.cpp new file mode 100644 index 000000000..ea90d357c --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_invite_link_widget.cpp @@ -0,0 +1,40 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "profile/profile_invite_link_widget.h" + +#include "styles/style_profile.h" +#include "lang.h" + +namespace Profile { + +InviteLinkWidget::InviteLinkWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_invite_link_section)) +{ + show(); +} + +int InviteLinkWidget::resizeGetHeight(int newWidth) { + int newHeight = contentTop(); + + return newHeight; +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_invite_link_widget.h b/Telegram/SourceFiles/profile/profile_invite_link_widget.h new file mode 100644 index 000000000..c9eada6a2 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_invite_link_widget.h @@ -0,0 +1,37 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "profile/profile_block_widget.h" + +namespace Profile { + +class InviteLinkWidget : public BlockWidget { +public: + InviteLinkWidget(QWidget *parent, PeerData *peer); + +protected: + // Resizes content and counts natural widget height for the desired width. + int resizeGetHeight(int newWidth) override; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_members_widget.cpp b/Telegram/SourceFiles/profile/profile_members_widget.cpp new file mode 100644 index 000000000..26ccc9308 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_members_widget.cpp @@ -0,0 +1,51 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "profile/profile_members_widget.h" + +#include "styles/style_profile.h" +#include "lang.h" + +namespace Profile { + +MembersWidget::MembersWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_participants_section)) +{ + show(); +} + +int MembersWidget::resizeGetHeight(int newWidth) { + int newHeight = contentTop(); + + return newHeight; +} + +ChannelMembersWidget::ChannelMembersWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_participants_section)) +{ + show(); +} + +int ChannelMembersWidget::resizeGetHeight(int newWidth) { + int newHeight = contentTop(); + + return newHeight; +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_members_widget.h b/Telegram/SourceFiles/profile/profile_members_widget.h new file mode 100644 index 000000000..8278091c1 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_members_widget.h @@ -0,0 +1,47 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "profile/profile_block_widget.h" + +namespace Profile { + +class MembersWidget : public BlockWidget { +public: + MembersWidget(QWidget *parent, PeerData *peer); + +protected: + // Resizes content and counts natural widget height for the desired width. + int resizeGetHeight(int newWidth) override; + +}; + +class ChannelMembersWidget : public BlockWidget { +public: + ChannelMembersWidget(QWidget *parent, PeerData *peer); + +protected: + // Resizes content and counts natural widget height for the desired width. + int resizeGetHeight(int newWidth) override; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_settings_widget.cpp b/Telegram/SourceFiles/profile/profile_settings_widget.cpp new file mode 100644 index 000000000..0b987f26d --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_settings_widget.cpp @@ -0,0 +1,40 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "profile/profile_settings_widget.h" + +#include "styles/style_profile.h" +#include "lang.h" + +namespace Profile { + +SettingsWidget::SettingsWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_settings_section)) +{ + show(); +} + +int SettingsWidget::resizeGetHeight(int newWidth) { + int newHeight = contentTop(); + + return newHeight; +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_settings_widget.h b/Telegram/SourceFiles/profile/profile_settings_widget.h new file mode 100644 index 000000000..76badc91b --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_settings_widget.h @@ -0,0 +1,37 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "profile/profile_block_widget.h" + +namespace Profile { + +class SettingsWidget : public BlockWidget { +public: + SettingsWidget(QWidget *parent, PeerData *peer); + +protected: + // Resizes content and counts natural widget height for the desired width. + int resizeGetHeight(int newWidth) override; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_shared_media_widget.cpp b/Telegram/SourceFiles/profile/profile_shared_media_widget.cpp new file mode 100644 index 000000000..5215e86fc --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_shared_media_widget.cpp @@ -0,0 +1,40 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "profile/profile_shared_media_widget.h" + +#include "styles/style_profile.h" +#include "lang.h" + +namespace Profile { + +SharedMediaWidget::SharedMediaWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_shared_media)) +{ + show(); +} + +int SharedMediaWidget::resizeGetHeight(int newWidth) { + int newHeight = contentTop(); + + return newHeight; +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_shared_media_widget.h b/Telegram/SourceFiles/profile/profile_shared_media_widget.h new file mode 100644 index 000000000..49597f69e --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_shared_media_widget.h @@ -0,0 +1,37 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "profile/profile_block_widget.h" + +namespace Profile { + +class SharedMediaWidget : public BlockWidget { +public: + SharedMediaWidget(QWidget *parent, PeerData *peer); + +protected: + // Resizes content and counts natural widget height for the desired width. + int resizeGetHeight(int newWidth) override; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index 6768a2b36..a72d5c404 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -115,7 +115,7 @@ ProfileInner::ProfileInner(ProfileWidget *profile, PeerData *peer) : TWidget(0) if (_peerUser->blocked == UserIsBlocked) { _blockUser.setText(lang(_peerUser->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user)); } - _phoneText = App::formatPhone(_peerUser->phone.isEmpty() ? App::phoneFromSharedContact(peerToUser(_peerUser->id)) : _peerUser->phone); + _phoneText = App::formatPhone(_peerUser->phone().isEmpty() ? App::phoneFromSharedContact(peerToUser(_peerUser->id)) : _peerUser->phone()); PhotoData *userPhoto = (_peerUser->photoId && _peerUser->photoId != UnknownPeerPhotoId) ? App::photo(_peerUser->photoId) : 0; if (userPhoto && userPhoto->date) { _photoLink.reset(new PhotoOpenClickHandler(userPhoto, _peer)); @@ -168,7 +168,7 @@ ProfileInner::ProfileInner(ProfileWidget *profile, PeerData *peer) : TWidget(0) QString maxStr; if (_peerUser->botInfo && !_peerUser->botInfo->cantJoinGroups) { maxStr = lang(_sendMessage.textWidth() > _inviteToGroup.textWidth() ? lng_profile_send_message : lng_profile_invite_to_group); - } else if (!_peerUser->phone.isEmpty()) { + } else if (!_peerUser->phone().isEmpty()) { maxStr = lang(_sendMessage.textWidth() > _shareContact.textWidth() ? lng_profile_send_message : lng_profile_share_contact); } else { maxStr = lang(lng_profile_send_message); @@ -193,13 +193,13 @@ ProfileInner::ProfileInner(ProfileWidget *profile, PeerData *peer) : TWidget(0) // about if (_peerUser) { - if (!_peerUser->about.isEmpty()) { - _about.setText(st::linkFont, _peerUser->about, _peerUser->botInfo ? _historyBotNoMonoOptions : _historyTextNoMonoOptions); + if (!_peerUser->about().isEmpty()) { + _about.setText(st::linkFont, _peerUser->about(), _peerUser->botInfo ? _historyBotNoMonoOptions : _historyTextNoMonoOptions); } updateBotLinksVisibility(); } else { - if (_peerChannel && !_peerChannel->about.isEmpty()) { - _about.setText(st::linkFont, _peerChannel->about, _historyTextNoMonoOptions); + if (_peerChannel && !_peerChannel->about().isEmpty()) { + _about.setText(st::linkFont, _peerChannel->about(), _historyTextNoMonoOptions); } _botSettings.hide(); _botHelp.hide(); @@ -538,10 +538,10 @@ void ProfileInner::onFullPeerUpdated(PeerData *peer) { _photoLink.clear(); } if (_peerUser) { - if (_peerUser->about.isEmpty()) { + if (_peerUser->about().isEmpty()) { _about = Text(st::wndMinWidth - st::profilePadding.left() - st::profilePadding.right()); } else { - _about.setText(st::linkFont, _peerUser->about, _peerUser->botInfo ? _historyBotNoMonoOptions : _historyTextNoMonoOptions); + _about.setText(st::linkFont, _peerUser->about(), _peerUser->botInfo ? _historyBotNoMonoOptions : _historyTextNoMonoOptions); } updateBotLinksVisibility(); resizeEvent(0); @@ -557,10 +557,10 @@ void ProfileInner::onFullPeerUpdated(PeerData *peer) { _members.setText(lng_channel_members_link(lt_count, (_peerChannel->count > 0) ? _peerChannel->count : 1)); _admins.setText(lng_channel_admins_link(lt_count, (_peerChannel->adminsCount > 0) ? _peerChannel->adminsCount : 1)); _onlineText = (_peerChannel->count > 0) ? lng_chat_status_members(lt_count, _peerChannel->count) : lang(_peerChannel->isMegagroup() ? lng_group_status : lng_channel_status); - if (_peerChannel->about.isEmpty()) { + if (_peerChannel->about().isEmpty()) { _about = Text(st::wndMinWidth - st::profilePadding.left() - st::profilePadding.right()); } else { - _about.setText(st::linkFont, _peerChannel->about, _historyTextNoMonoOptions); + _about.setText(st::linkFont, _peerChannel->about(), _historyTextNoMonoOptions); } showAll(); resizeEvent(0); @@ -607,7 +607,7 @@ void ProfileInner::peerUpdated(PeerData *data) { if (data == _peer) { PhotoData *photo = 0; if (_peerUser) { - _phoneText = App::formatPhone(_peerUser->phone.isEmpty() ? App::phoneFromSharedContact(peerToUser(_peerUser->id)) : _peerUser->phone); + _phoneText = App::formatPhone(_peerUser->phone().isEmpty() ? App::phoneFromSharedContact(peerToUser(_peerUser->id)) : _peerUser->phone()); if (_peerUser->photoId && _peerUser->photoId != UnknownPeerPhotoId) photo = App::photo(_peerUser->photoId); if (_wasBlocked != _peerUser->blocked) { _wasBlocked = _peerUser->blocked; @@ -1668,7 +1668,7 @@ void ProfileInner::showAll() { _createInvitationLink.hide(); _invitationLink.hide(); _sendMessage.show(); - if (_peerUser->phone.isEmpty()) { + if (_peerUser->phone().isEmpty()) { _shareContact.hide(); if (_peerUser->botInfo && !_peerUser->botInfo->cantJoinGroups) { _inviteToGroup.show(); @@ -1950,7 +1950,7 @@ void ProfileWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { p.drawSprite(QPoint(st::topBarBackPadding.left(), (st::topBarHeight - st::topBarBackImg.pxHeight()) / 2), st::topBarBackImg); p.setFont(st::topBarBackFont->f); p.setPen(st::topBarBackColor->p); - p.drawText(st::topBarBackPadding.left() + st::topBarBackImg.pxWidth() + st::topBarBackPadding.right(), (st::topBarHeight - st::topBarBackFont->height) / 2 + st::topBarBackFont->ascent, lang(peer()->isUser() ? lng_profile_info : ((peer()->isChat() || peer()->isMegagroup()) ? lng_profile_group_info : lng_profile_channel_info))); +// p.drawText(st::topBarBackPadding.left() + st::topBarBackImg.pxWidth() + st::topBarBackPadding.right(), (st::topBarHeight - st::topBarBackFont->height) / 2 + st::topBarBackFont->ascent, lang(peer()->isUser() ? lng_profile_info : ((peer()->isChat() || peer()->isMegagroup()) ? lng_profile_group_info : lng_profile_channel_info))); } void ProfileWidget::topBarClick() { diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp index c08a12ab6..963f8fb9b 100644 --- a/Telegram/SourceFiles/settingswidget.cpp +++ b/Telegram/SourceFiles/settingswidget.cpp @@ -123,7 +123,7 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : TWidget(parent) , _a_photo(animation(this, &SettingsInner::step_photo)) // contact info -, _phoneText(self() ? App::formatPhone(self()->phone) : QString()) +, _phoneText(self() ? App::formatPhone(self()->phone()) : QString()) , _chooseUsername(this, (self() && !self()->username.isEmpty()) ? ('@' + self()->username) : lang(lng_settings_choose_username)) // notifications diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 2d0971297..d91ee4150 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -262,6 +262,18 @@ void PeerData::fillNames() { } } +bool UserData::setAbout(const QString &newAbout) { + if (_about == newAbout) { + return false; + } + + _about = newAbout; + Notify::PeerUpdate update(this); + update.flags |= Notify::PeerUpdateFlag::AboutChanged; + Notify::peerUpdatedDelayed(update); + return true; +} + void UserData::setName(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) { setNameDelayed(newFirstName, newLastName, newPhoneName, newUsername); Notify::peerUpdatedSendDelayed(); @@ -286,7 +298,7 @@ void UserData::setNameDelayed(const QString &newFirstName, const QString &newLas } void UserData::setPhone(const QString &newPhone) { - phone = newPhone; + _phone = newPhone; } void UserData::setBotInfoVersion(int version) { @@ -480,6 +492,18 @@ void ChannelData::fullUpdated() { _lastFullUpdate = getms(true); } +bool ChannelData::setAbout(const QString &newAbout) { + if (_about == newAbout) { + return false; + } + + _about = newAbout; + Notify::PeerUpdate update(this); + update.flags |= Notify::PeerUpdateFlag::AboutChanged; + Notify::peerUpdatedDelayed(update); + return true; +} + void ChannelData::flagsUpdated() { if (isMegagroup()) { if (!mgInfo) { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index e14eb290b..bcb4537ca 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -442,7 +442,7 @@ public: // When actually trying to share contact we perform // a full check by canShareThisContact() call. bool canShareThisContactFast() const { - return !phone.isEmpty(); + return !_phone.isEmpty(); } MTPInputUser inputUser; @@ -450,7 +450,9 @@ public: QString firstName; QString lastName; QString username; - QString phone; + const QString &phone() const { + return _phone; + } QString nameOrPhone; Text phoneText; TimeId onlineTill = 0; @@ -461,7 +463,10 @@ public: Photos photos; int photosCount = -1; // -1 not loaded, 0 all loaded - QString about; + bool setAbout(const QString &newAbout); + const QString &about() const { + return _about; + } BotInfo *botInfo = nullptr; @@ -474,6 +479,8 @@ public: private: QString _restrictionReason; + QString _about; + QString _phone; }; @@ -669,7 +676,13 @@ public: MTPinputChannel inputChannel; - QString username, about; + QString username; + + // Returns true if about text was changed. + bool setAbout(const QString &newAbout); + const QString &about() const { + return _about; + } int count = 1; int adminsCount = 1; @@ -791,11 +804,11 @@ public: ~ChannelData(); private: - PtsWaiter _ptsWaiter; uint64 _lastFullUpdate = 0; QString _restrictionReason; + QString _about; }; diff --git a/Telegram/SourceFiles/ui/flatinput.cpp b/Telegram/SourceFiles/ui/flatinput.cpp index 1afe8650e..dbb8f3006 100644 --- a/Telegram/SourceFiles/ui/flatinput.cpp +++ b/Telegram/SourceFiles/ui/flatinput.cpp @@ -2472,9 +2472,9 @@ void PhoneInput::focusInEvent(QFocusEvent *e) { void PhoneInput::clearText() { QString phone; if (App::self()) { - QVector<int> newPattern = phoneNumberParse(App::self()->phone); + QVector<int> newPattern = phoneNumberParse(App::self()->phone()); if (!newPattern.isEmpty()) { - phone = App::self()->phone.mid(0, newPattern.at(0)); + phone = App::self()->phone().mid(0, newPattern.at(0)); } } setText(phone); diff --git a/Telegram/SourceFiles/ui/flatlabel.cpp b/Telegram/SourceFiles/ui/flatlabel.cpp index a4894247c..b30fbd94d 100644 --- a/Telegram/SourceFiles/ui/flatlabel.cpp +++ b/Telegram/SourceFiles/ui/flatlabel.cpp @@ -31,11 +31,36 @@ namespace { 0, // maxh Qt::LayoutDirectionAuto, // dir }; + TextParseOptions _labelMarkedOptions = { + TextParseMultiline | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir + }; } -FlatLabel::FlatLabel(QWidget *parent, const QString &text, const style::flatLabel &st, const style::textStyle &tst) : TWidget(parent), -_text(st.width ? st.width : QFIXED_MAX), _st(st), _tst(tst), _opacity(1) { - setRichText(text); +FlatLabel::FlatLabel(QWidget *parent, const style::flatLabel &st, const style::textStyle &tst) : TWidget(parent) +, _text(st.width ? st.width : QFIXED_MAX) +, _st(st) +, _tst(tst) +, _contextCopyText(lang(lng_context_copy_text)) { + init(); +} + +FlatLabel::FlatLabel(QWidget *parent, const QString &text, InitType initType, const style::flatLabel &st, const style::textStyle &tst) : TWidget(parent) +, _text(st.width ? st.width : QFIXED_MAX) +, _st(st) +, _tst(tst) +, _contextCopyText(lang(lng_context_copy_text)) { + if (initType == InitType::Rich) { + setRichText(text); + } else { + setText(text); + } + init(); +} + +void FlatLabel::init() { _trippleClickTimer.setSingleShot(true); _touchSelectTimer.setSingleShot(true); @@ -58,15 +83,31 @@ void FlatLabel::setRichText(const QString &text) { setMouseTracking(_selectable || _text.hasLinks()); } +void FlatLabel::setMarkedText(const TextWithEntities &textWithEntities) { + textstyleSet(&_tst); + _text.setMarkedText(_st.font, textWithEntities, _labelMarkedOptions); + refreshSize(); + textstyleRestore(); + setMouseTracking(_selectable || _text.hasLinks()); +} + void FlatLabel::setSelectable(bool selectable) { _selectable = selectable; setMouseTracking(_selectable || _text.hasLinks()); } +void FlatLabel::setDoubleClickSelectsParagraph(bool doubleClickSelectsParagraph) { + _doubleClickSelectsParagraph = doubleClickSelectsParagraph; +} + void FlatLabel::setContextCopyText(const QString ©Text) { _contextCopyText = copyText; } +void FlatLabel::setExpandLinksMode(ExpandLinksMode mode) { + _contextExpandLinksMode = mode; +} + void FlatLabel::resizeToWidth(int32 width) { textstyleSet(&_tst); _allowedWidth = width; @@ -74,6 +115,10 @@ void FlatLabel::resizeToWidth(int32 width) { textstyleRestore(); } +int FlatLabel::naturalWidth() const { + return _text.maxWidth(); +} + int FlatLabel::countTextWidth() const { return _allowedWidth ? (_allowedWidth - _st.margin.left() - _st.margin.right()) : (_st.width ? _st.width : _text.maxWidth()); } @@ -95,6 +140,10 @@ void FlatLabel::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { _text.setLink(lnkIndex, lnk); } +void FlatLabel::setClickHandlerHook(ClickHandlerHook &&hook) { + _clickHandlerHook = std_::move(hook); +} + void FlatLabel::mouseMoveEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); dragActionUpdate(); @@ -177,7 +226,9 @@ Text::StateResult FlatLabel::dragActionFinish(const QPoint &p, Qt::MouseButton b _selectionType = TextSelectType::Letters; if (activated) { - App::activateClickHandler(activated, button); + if (_clickHandlerHook.isNull() || _clickHandlerHook.call(activated, button)) { + App::activateClickHandler(activated, button); + } } return state; } @@ -194,7 +245,7 @@ void FlatLabel::mouseDoubleClickEvent(QMouseEvent *e) { if (((_dragAction == Selecting) || (_dragAction == NoDrag)) && _selectionType == TextSelectType::Letters) { if (state.uponSymbol) { _dragSymbol = state.symbol; - _selectionType = TextSelectType::Words; + _selectionType = _doubleClickSelectsParagraph ? TextSelectType::Paragraphs : TextSelectType::Words; if (_dragAction == NoDrag) { _dragAction = Selecting; _selection = { state.symbol, state.symbol }; @@ -219,7 +270,9 @@ void FlatLabel::leaveEvent(QEvent *e) { void FlatLabel::focusOutEvent(QFocusEvent *e) { if (!_selection.empty()) { - _savedSelection = _selection; + if (_contextMenu) { + _savedSelection = _selection; + } _selection = { 0, 0 }; update(); } @@ -338,12 +391,12 @@ void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason) _contextMenuClickHandler = ClickHandler::getActive(); - if (fullSelection) { - _contextMenu->addAction(contextCopyText(), this, SLOT(onCopyContextText()))->setEnabled(true); - } else if (uponSelection) { + if (fullSelection && !_contextCopyText.isEmpty()) { + _contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText()))->setEnabled(true); + } else if (uponSelection && !fullSelection) { _contextMenu->addAction(lang(lng_context_copy_selected), this, SLOT(onCopySelectedText()))->setEnabled(true); - } else if (!hasSelection) { - _contextMenu->addAction(contextCopyText(), this, SLOT(onCopyContextText()))->setEnabled(true); + } else if (!hasSelection && !_contextCopyText.isEmpty()) { + _contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText()))->setEnabled(true); } QString linkCopyToClipboardText = _contextMenuClickHandler ? _contextMenuClickHandler->copyToClipboardContextItemText() : QString(); @@ -361,19 +414,15 @@ void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason) } } -QString FlatLabel::contextCopyText() const { - return _contextCopyText.isEmpty() ? lang(lng_context_copy_text) : _contextCopyText; -} - void FlatLabel::onCopySelectedText() { auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection; if (!selection.empty()) { - QApplication::clipboard()->setText(_text.originalText(selection, ExpandLinksAll)); + QApplication::clipboard()->setText(_text.originalText(selection, _contextExpandLinksMode)); } } void FlatLabel::onCopyContextText() { - QApplication::clipboard()->setText(_text.originalText({ 0, 0xFFFF }, ExpandLinksAll)); + QApplication::clipboard()->setText(_text.originalText({ 0, 0xFFFF }, _contextExpandLinksMode)); } void FlatLabel::onCopyContextUrl() { @@ -514,7 +563,7 @@ Text::StateResult FlatLabel::getTextState(const QPoint &m) const { textstyleSet(&_tst); Text::StateResult state; - if (_st.maxHeight && _st.maxHeight < _fullTextHeight) { + if (_st.maxHeight && (_st.maxHeight < _fullTextHeight || textWidth < _text.maxWidth())) { auto lineHeight = qMax(_tst.lineHeight, _st.font->height); request.lines = qMax(_st.maxHeight / lineHeight, 1); state = _text.getStateElided(m.x() - _st.margin.left(), m.y() - _st.margin.top(), textWidth, request); @@ -538,7 +587,7 @@ void FlatLabel::paintEvent(QPaintEvent *e) { textstyleSet(&_tst); int textWidth = width() - _st.margin.left() - _st.margin.right(); auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection; - if (_st.maxHeight && _st.maxHeight < _fullTextHeight) { + if (_st.maxHeight && (_st.maxHeight < _fullTextHeight || textWidth < _text.maxWidth())) { auto lineHeight = qMax(_tst.lineHeight, _st.font->height); auto lines = qMax(_st.maxHeight / lineHeight, 1); _text.drawElided(p, _st.margin.left(), _st.margin.top(), textWidth, lines, _st.align, e->rect().y(), e->rect().bottom(), 0, false, selection); diff --git a/Telegram/SourceFiles/ui/flatlabel.h b/Telegram/SourceFiles/ui/flatlabel.h index 592065229..097213560 100644 --- a/Telegram/SourceFiles/ui/flatlabel.h +++ b/Telegram/SourceFiles/ui/flatlabel.h @@ -24,19 +24,32 @@ class FlatLabel : public TWidget, public ClickHandlerHost { Q_OBJECT public: - FlatLabel(QWidget *parent, const QString &text, const style::flatLabel &st = st::labelDefFlat, const style::textStyle &tst = st::defaultTextStyle); + FlatLabel(QWidget *parent, const style::flatLabel &st = st::labelDefFlat, const style::textStyle &tst = st::defaultTextStyle); + + enum class InitType { + Simple, + Rich, + }; + FlatLabel(QWidget *parent, const QString &text, InitType initType, const style::flatLabel &st = st::labelDefFlat, const style::textStyle &tst = st::defaultTextStyle); void setOpacity(float64 o); void setText(const QString &text); void setRichText(const QString &text); + void setMarkedText(const TextWithEntities &textWithEntities); void setSelectable(bool selectable); + void setDoubleClickSelectsParagraph(bool doubleClickSelectsParagraph); void setContextCopyText(const QString ©Text); + void setExpandLinksMode(ExpandLinksMode mode); void resizeToWidth(int32 width); + int naturalWidth() const; void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); + using ClickHandlerHook = Function<bool, const ClickHandlerPtr &, Qt::MouseButton>; + void setClickHandlerHook(ClickHandlerHook &&hook); + // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) override; @@ -67,6 +80,8 @@ private slots: void onExecuteDrag(); private: + void init(); + Text::StateResult dragActionUpdate(); Text::StateResult dragActionStart(const QPoint &p, Qt::MouseButton button); Text::StateResult dragActionFinish(const QPoint &p, Qt::MouseButton button); @@ -83,12 +98,11 @@ private: FromTouch, }; void showContextMenu(QContextMenuEvent *e, ContextMenuReason reason); - QString contextCopyText() const; Text _text; style::flatLabel _st; style::textStyle _tst; - float64 _opacity; + float64 _opacity = 1.; int _allowedWidth = 0; int _fullTextHeight = 0; @@ -97,6 +111,7 @@ private: bool _selectable = false; TextSelection _selection, _savedSelection; TextSelectType _selectionType = TextSelectType::Letters; + bool _doubleClickSelectsParagraph = false; enum DragAction { NoDrag = 0x00, @@ -117,6 +132,9 @@ private: PopupMenu *_contextMenu = nullptr; ClickHandlerPtr _contextMenuClickHandler; QString _contextCopyText; + ExpandLinksMode _contextExpandLinksMode = ExpandLinksAll; + + ClickHandlerHook _clickHandlerHook; // text selection and context menu by touch support (at least Windows Surface tablets) bool _touchSelect = false; diff --git a/Telegram/SourceFiles/ui/twidget.h b/Telegram/SourceFiles/ui/twidget.h index 7b6935568..680ad4c86 100644 --- a/Telegram/SourceFiles/ui/twidget.h +++ b/Telegram/SourceFiles/ui/twidget.h @@ -312,6 +312,20 @@ public: return _widget; } + void destroy() { + if (_widget) { + delete _widget; + _widget = nullptr; + } + } + void destroyDelayed() { + if (_widget) { + _widget->hide(); + _widget->deleteLater(); + _widget = nullptr; + } + } + private: T *_widget; diff --git a/Telegram/SourceFiles/window/top_bar_widget.cpp b/Telegram/SourceFiles/window/top_bar_widget.cpp index c175611f0..2fbb27e5b 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.cpp +++ b/Telegram/SourceFiles/window/top_bar_widget.cpp @@ -87,7 +87,7 @@ void TopBarWidget::onInfoClicked() { void TopBarWidget::onAddContact() { PeerData *p = nullptr;// App::main() ? App::main()->profilePeer() : 0; UserData *u = p ? p->asUser() : 0; - if (u) Ui::showLayer(new AddContactBox(u->firstName, u->lastName, u->phone.isEmpty() ? App::phoneFromSharedContact(peerToUser(u->id)) : u->phone)); + if (u) Ui::showLayer(new AddContactBox(u->firstName, u->lastName, u->phone().isEmpty() ? App::phoneFromSharedContact(peerToUser(u->id)) : u->phone())); } void TopBarWidget::onEdit() { diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 17d801473..345141923 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -1213,12 +1213,18 @@ <ClCompile Include="SourceFiles\passcodewidget.cpp" /> <ClCompile Include="SourceFiles\playerwidget.cpp" /> <ClCompile Include="SourceFiles\profilewidget.cpp" /> + <ClCompile Include="SourceFiles\profile\profile_actions_widget.cpp" /> <ClCompile Include="SourceFiles\profile\profile_block_widget.cpp" /> <ClCompile Include="SourceFiles\profile\profile_cover.cpp" /> <ClCompile Include="SourceFiles\profile\profile_cover_drop_area.cpp" /> <ClCompile Include="SourceFiles\profile\profile_fixed_bar.cpp" /> + <ClCompile Include="SourceFiles\profile\profile_info_widget.cpp" /> <ClCompile Include="SourceFiles\profile\profile_inner_widget.cpp" /> + <ClCompile Include="SourceFiles\profile\profile_invite_link_widget.cpp" /> + <ClCompile Include="SourceFiles\profile\profile_members_widget.cpp" /> <ClCompile Include="SourceFiles\profile\profile_section_memento.cpp" /> + <ClCompile Include="SourceFiles\profile\profile_settings_widget.cpp" /> + <ClCompile Include="SourceFiles\profile\profile_shared_media_widget.cpp" /> <ClCompile Include="SourceFiles\profile\profile_userpic_button.cpp" /> <ClCompile Include="SourceFiles\profile\profile_widget.cpp" /> <ClCompile Include="SourceFiles\pspecific_linux.cpp"> @@ -1549,21 +1555,27 @@ <Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_fixed_bar.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include"</Command> </CustomBuild> <CustomBuild Include="SourceFiles\profile\profile_block_widget.h"> - <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs> + <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs> <Message Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">Moc%27ing profile_block_widget.h...</Message> <Outputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs> <Command Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_block_widget.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include"</Command> - <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs> + <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs> <Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Moc%27ing profile_block_widget.h...</Message> <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs> <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_block_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include"</Command> - <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs> + <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs> <Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Moc%27ing profile_block_widget.h...</Message> <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs> <Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_block_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include"</Command> </CustomBuild> + <ClInclude Include="SourceFiles\profile\profile_actions_widget.h" /> <ClInclude Include="SourceFiles\profile\profile_cover_drop_area.h" /> + <ClInclude Include="SourceFiles\profile\profile_info_widget.h" /> + <ClInclude Include="SourceFiles\profile\profile_invite_link_widget.h" /> + <ClInclude Include="SourceFiles\profile\profile_members_widget.h" /> <ClInclude Include="SourceFiles\profile\profile_section_memento.h" /> + <ClInclude Include="SourceFiles\profile\profile_settings_widget.h" /> + <ClInclude Include="SourceFiles\profile\profile_shared_media_widget.h" /> <ClInclude Include="SourceFiles\profile\profile_userpic_button.h" /> <ClInclude Include="SourceFiles\serialize\serialize_common.h" /> <ClInclude Include="SourceFiles\serialize\serialize_document.h" /> diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 7412f0303..6d0474c9b 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -1188,18 +1188,6 @@ <ClCompile Include="SourceFiles\profile\profile_section_memento.cpp"> <Filter>SourceFiles\profile</Filter> </ClCompile> - <ClCompile Include="GeneratedFiles\Deploy\moc_profile_block_widget.cpp"> - <Filter>GeneratedFiles\Deploy</Filter> - </ClCompile> - <ClCompile Include="GeneratedFiles\Debug\moc_profile_block_widget.cpp"> - <Filter>GeneratedFiles\Debug</Filter> - </ClCompile> - <ClCompile Include="GeneratedFiles\Release\moc_profile_block_widget.cpp"> - <Filter>GeneratedFiles\Release</Filter> - </ClCompile> - <ClCompile Include="SourceFiles\profile\profile_block_widget.cpp"> - <Filter>SourceFiles\profile</Filter> - </ClCompile> <ClCompile Include="SourceFiles\ui\buttons\round_button.cpp"> <Filter>SourceFiles\ui\buttons</Filter> </ClCompile> @@ -1215,6 +1203,36 @@ <ClCompile Include="SourceFiles\profile\profile_userpic_button.cpp"> <Filter>SourceFiles\profile</Filter> </ClCompile> + <ClCompile Include="GeneratedFiles\Deploy\moc_profile_block_widget.cpp"> + <Filter>GeneratedFiles\Deploy</Filter> + </ClCompile> + <ClCompile Include="GeneratedFiles\Debug\moc_profile_block_widget.cpp"> + <Filter>GeneratedFiles\Debug</Filter> + </ClCompile> + <ClCompile Include="GeneratedFiles\Release\moc_profile_block_widget.cpp"> + <Filter>GeneratedFiles\Release</Filter> + </ClCompile> + <ClCompile Include="SourceFiles\profile\profile_block_widget.cpp"> + <Filter>SourceFiles\profile</Filter> + </ClCompile> + <ClCompile Include="SourceFiles\profile\profile_info_widget.cpp"> + <Filter>SourceFiles\profile</Filter> + </ClCompile> + <ClCompile Include="SourceFiles\profile\profile_actions_widget.cpp"> + <Filter>SourceFiles\profile</Filter> + </ClCompile> + <ClCompile Include="SourceFiles\profile\profile_invite_link_widget.cpp"> + <Filter>SourceFiles\profile</Filter> + </ClCompile> + <ClCompile Include="SourceFiles\profile\profile_members_widget.cpp"> + <Filter>SourceFiles\profile</Filter> + </ClCompile> + <ClCompile Include="SourceFiles\profile\profile_settings_widget.cpp"> + <Filter>SourceFiles\profile</Filter> + </ClCompile> + <ClCompile Include="SourceFiles\profile\profile_shared_media_widget.cpp"> + <Filter>SourceFiles\profile</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="SourceFiles\stdafx.h"> @@ -1418,6 +1436,24 @@ <ClInclude Include="SourceFiles\profile\profile_userpic_button.h"> <Filter>SourceFiles\profile</Filter> </ClInclude> + <ClInclude Include="SourceFiles\profile\profile_info_widget.h"> + <Filter>SourceFiles\profile</Filter> + </ClInclude> + <ClInclude Include="SourceFiles\profile\profile_actions_widget.h"> + <Filter>SourceFiles\profile</Filter> + </ClInclude> + <ClInclude Include="SourceFiles\profile\profile_invite_link_widget.h"> + <Filter>SourceFiles\profile</Filter> + </ClInclude> + <ClInclude Include="SourceFiles\profile\profile_members_widget.h"> + <Filter>SourceFiles\profile</Filter> + </ClInclude> + <ClInclude Include="SourceFiles\profile\profile_settings_widget.h"> + <Filter>SourceFiles\profile</Filter> + </ClInclude> + <ClInclude Include="SourceFiles\profile\profile_shared_media_widget.h"> + <Filter>SourceFiles\profile</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <CustomBuild Include="SourceFiles\application.h">