Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94cf307ae0 | ||
|
|
2cc1fde5e4 | ||
|
|
6796ac688a | ||
|
|
9551cfaf9b | ||
|
|
8ef9ec0567 | ||
|
|
af552fb4c0 | ||
|
|
ae7e5be5cd | ||
|
|
74b126f309 | ||
|
|
26e023058c | ||
|
|
6236590ca4 | ||
|
|
ea51f976f2 | ||
|
|
719f3428ec | ||
|
|
2df4d19474 | ||
|
|
2a409e3734 | ||
|
|
0171a4e874 | ||
|
|
59e5ffe743 | ||
|
|
2bcbb5a5be | ||
|
|
5b4694a4eb | ||
|
|
54d6673d0b | ||
|
|
e07a7a4b4c | ||
|
|
f2d11e7432 | ||
|
|
1a115cc7e5 | ||
|
|
a00941f7ce | ||
|
|
634d21e486 | ||
|
|
95d8742e3c | ||
|
|
bd8dee0972 | ||
|
|
b34099f49e | ||
|
|
0380e66c30 | ||
|
|
907b6f0a78 | ||
|
|
21f4bbbe7b | ||
|
|
69d9072ff0 | ||
|
|
d5ae9bcba2 | ||
|
|
86c0dfb295 | ||
|
|
01821c30e5 | ||
|
|
7f66e0fdfe | ||
|
|
2569df9e5a | ||
|
|
5f8143e6a4 | ||
|
|
1fc7dabd3e | ||
|
|
5d18d7c813 | ||
|
|
4e8f5541af | ||
|
|
a8ac18e4fd | ||
|
|
a6c15217c0 | ||
|
|
57351dd42a | ||
|
|
58d21ff916 | ||
|
|
8e45b09083 | ||
|
|
44014e62ba | ||
|
|
ff65daa9fe | ||
|
|
ec515080b5 | ||
|
|
255dbf9405 | ||
|
|
aebb40dc1e | ||
|
|
b20c2b4774 | ||
|
|
3b3a705a67 | ||
|
|
711aa51046 | ||
|
|
4d54cf1370 | ||
|
|
e023092744 | ||
|
|
2e421e8aed | ||
|
|
afe9d38c48 | ||
|
|
3f751bfbb0 | ||
|
|
b1f33890d6 | ||
|
|
92333e982c | ||
|
|
16ca2d39c5 | ||
|
|
977dee3599 | ||
|
|
546766fb13 | ||
|
|
ddf4a36bdc | ||
|
|
fa3a76b3d8 | ||
|
|
d5de064019 | ||
|
|
37b018257e | ||
|
|
14034c255e | ||
|
|
cbbccd0364 | ||
|
|
b8204a317d | ||
|
|
499e3113b9 | ||
|
|
656e4869e6 | ||
|
|
defec611e3 | ||
|
|
49def354bd | ||
|
|
712b3f481c | ||
|
|
b3a723c871 | ||
|
|
de16a66a4a | ||
|
|
b0f191515a | ||
|
|
89ccaccb88 | ||
|
|
1f070da202 | ||
|
|
963e969d2a | ||
|
|
4734700ac5 | ||
|
|
d9da2edd7c | ||
|
|
6d48ca850e | ||
|
|
3e7ac7eb26 | ||
|
|
520a644150 | ||
|
|
3a56b7cabd | ||
|
|
efa72578cd | ||
|
|
b6087ce7ce | ||
|
|
537400d8b2 | ||
|
|
4c9931ab02 | ||
|
|
0a4038d061 | ||
|
|
2d5188b968 | ||
|
|
4bab7583ba | ||
|
|
b2f29b674d | ||
|
|
574f4a73cb | ||
|
|
05e3ddce0c | ||
|
|
3c101b0a50 | ||
|
|
e998bd0b3f | ||
|
|
251176df47 | ||
|
|
97c15865a5 | ||
|
|
9d4558de2b | ||
|
|
38f7f48c17 | ||
|
|
9534121676 | ||
|
|
10b76d921b |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -10,3 +10,6 @@
|
||||
[submodule "Telegram/ThirdParty/Catch"]
|
||||
path = Telegram/ThirdParty/Catch
|
||||
url = https://github.com/philsquared/Catch
|
||||
[submodule "Telegram/ThirdParty/crl"]
|
||||
path = Telegram/ThirdParty/crl
|
||||
url = https://github.com/telegramdesktop/crl.git
|
||||
|
||||
@@ -196,7 +196,6 @@ outSemiboldPalette: TextPalette(outTextPalette) {
|
||||
selectLinkFg: msgOutServiceFgSelected;
|
||||
}
|
||||
|
||||
mediaPadding: margins(0px, 0px, 0px, 0px);
|
||||
mediaCaptionSkip: 5px;
|
||||
mediaInBubbleSkip: 5px;
|
||||
mediaThumbSize: 48px;
|
||||
|
||||
@@ -27,7 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_menu_about" = "About";
|
||||
"lng_menu_update" = "Update";
|
||||
"lng_menu_back" = "Back";
|
||||
"lng_menu_night_mode" = "Night mode";
|
||||
"lng_menu_night_mode" = "Night Mode";
|
||||
|
||||
"lng_disable_notifications_from_tray" = "Disable notifications";
|
||||
"lng_enable_notifications_from_tray" = "Enable notifications";
|
||||
@@ -125,9 +125,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_flood_error" = "Too many tries. Please try again later.";
|
||||
"lng_gif_error" = "An error has occurred while reading GIF animation :(";
|
||||
"lng_edit_error" = "You cannot edit this message";
|
||||
"lng_join_channel_error" = "Sorry, you have joined too many channels and supergroups. Please leave some before joining.";
|
||||
"lng_join_channel_error" = "Sorry, you have joined too many channels and supergroups. Please leave some before joining this one.";
|
||||
"lng_error_phone_flood" = "Sorry, you have deleted and re-created your account too many times recently. Please wait for a few days before signing up again.";
|
||||
"lng_error_start_minimized_passcoded" = "You have set a local passcode, so the app can't be launched minimized. App will ask you to enter the passcode before it can start working.";
|
||||
"lng_error_start_minimized_passcoded" = "You have set a local passcode, so Telegram Desktop can't be launched minimised; it will ask you to enter your passcode before it can start working.";
|
||||
"lng_error_pinned_max#one" = "Sorry, you can pin no more than {count} chat to the top.";
|
||||
"lng_error_pinned_max#other" = "Sorry, you can pin no more than {count} chats to the top.";
|
||||
"lng_error_public_groups_denied" = "Unfortunately, you were banned from participating in public groups.\n{more_info}";
|
||||
@@ -137,8 +137,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the blacklist and you can't unban them.";
|
||||
"lng_error_cant_ban_admin" = "Sorry, you can't ban this user because they are an admin in this group and you are not allowed to demote them.";
|
||||
"lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?";
|
||||
"lng_sure_add_admin_unban" = "This user is currently restricted or banned in this group. Are you sure you want to unban and promote them?";
|
||||
"lng_sure_ban_admin" = "This user is an admin in this group. Are you sure you want to go ahead and restrict them?";
|
||||
"lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?";
|
||||
"lng_sure_add_admin_unban" = "This user is currently restricted or banned. Are you sure you want to unban and promote them?";
|
||||
"lng_sure_ban_admin" = "This user is an admin. Are you sure you want to go ahead and restrict them?";
|
||||
"lng_sure_ban_user_group" = "Ban {user} in the group?";
|
||||
"lng_sure_enable_socks" = "Are you sure you want to enable this proxy?\n\nServer: {server}\nPort: {port}\n\nYou can change your proxy server later in the Settings (Connection Type).";
|
||||
"lng_sure_enable" = "Enable";
|
||||
@@ -166,8 +167,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_photo_caption" = "Caption";
|
||||
"lng_photos_comment" = "Comment";
|
||||
|
||||
"lng_phone_title" = "Your Phone";
|
||||
"lng_phone_desc" = "Please confirm your country code and\nenter your phone number.";
|
||||
"lng_phone_title" = "Your Phone Number";
|
||||
"lng_phone_desc" = "Please confirm your country code and\nenter your mobile phone number.";
|
||||
"lng_phone_notreg" = "If you don't have a Telegram account yet,\nplease [b]sign up[/b] with {link_start}Android / iPhone{link_end} or {signup_start}here{signup_end}";
|
||||
"lng_country_code" = "Country Code";
|
||||
"lng_bad_country_code" = "Invalid Country Code";
|
||||
@@ -179,7 +180,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_code_desc" = "We have sent you a message with activation\ncode to your phone. Please enter it below.";
|
||||
"lng_code_telegram" = "Please enter the code you've just received\nin your previous [b]Telegram[/b] app.";
|
||||
"lng_code_no_telegram" = "Send code via SMS";
|
||||
"lng_code_call" = "Telegram will dial your number in {minutes}:{seconds}";
|
||||
"lng_code_call" = "Telegram will call you in {minutes}:{seconds}";
|
||||
"lng_code_calling" = "Requesting a call from Telegram...";
|
||||
"lng_code_called" = "Telegram dialed your number";
|
||||
|
||||
@@ -187,7 +188,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_bad_phone_noreg" = "Phone number not registered.";
|
||||
"lng_bad_code" = "You have entered an invalid code.";
|
||||
"lng_bad_name" = "Please enter your first and last name.";
|
||||
"lng_bad_photo" = "Bad image selected.";
|
||||
"lng_bad_photo" = "Sorry, Telegram can't process that type of image.";
|
||||
|
||||
"lng_signin_title" = "Cloud password check";
|
||||
"lng_signin_desc" = "Please enter your cloud password.";
|
||||
@@ -201,8 +202,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_signin_bad_password" = "You have entered a wrong password.";
|
||||
"lng_signin_wrong_code" = "You have entered an invalid code.";
|
||||
"lng_signin_try_password" = "Having trouble accessing your e-mail?";
|
||||
"lng_signin_password_removed" = "Your cloud password was disabled.\nYou can set a new one in Settings.";
|
||||
"lng_signin_no_email_forgot" = "Since you haven't provided a recovery\ne-mail when setting up your password, your remaining options are either to remember your password or to reset your account.";
|
||||
"lng_signin_password_removed" = "Your cloud password was disabled.\nYou can set up a new one in Settings.";
|
||||
"lng_signin_no_email_forgot" = "Since you didn't provide a recovery e-mail when setting up your password, your remaining options are either to remember your password or to reset your account.";
|
||||
"lng_signin_cant_email_forgot" = "If you can't restore access to the e-mail, your remaining options are either to remember your password or to reset your account.";
|
||||
"lng_signin_reset_account" = "Reset your account";
|
||||
"lng_signin_sure_reset" = "Warning!\n\nYou will lose all your chats and messages, along with any media and files you shared!\n\nDo you want to reset your account?";
|
||||
@@ -251,10 +252,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_username_invalid" = "This username is invalid.";
|
||||
"lng_username_occupied" = "This username is already occupied.";
|
||||
"lng_username_too_short" = "This username is too short.";
|
||||
"lng_username_bad_symbols" = "This username has bad symbols.";
|
||||
"lng_username_bad_symbols" = "Only a-z, 0-9, and underscores allowed.";
|
||||
"lng_username_available" = "This username is available.";
|
||||
"lng_username_not_found" = "User @{user} not found.";
|
||||
"lng_username_link_willbe" = "Such link will open a chat with you:";
|
||||
"lng_username_link_willbe" = "This link will open a chat with you:";
|
||||
"lng_username_link" = "This link opens a chat with you:";
|
||||
"lng_username_copied" = "Link copied to clipboard.";
|
||||
|
||||
@@ -322,9 +323,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_settings_adaptive_wide" = "Adaptive layout for wide screens";
|
||||
|
||||
"lng_backgrounds_header" = "Choose your new chat background";
|
||||
"lng_theme_sure_keep" = "Keep this color theme?";
|
||||
"lng_theme_reverting#one" = "Reverting to the old color theme in {count} second.";
|
||||
"lng_theme_reverting#other" = "Reverting to the old color theme in {count} seconds.";
|
||||
"lng_theme_sure_keep" = "Keep this theme?";
|
||||
"lng_theme_reverting#one" = "Reverting to the old theme in {count} second.";
|
||||
"lng_theme_reverting#other" = "Reverting to the old theme in {count} seconds.";
|
||||
"lng_theme_keep_changes" = "Keep changes";
|
||||
"lng_theme_revert" = "Revert";
|
||||
|
||||
@@ -339,7 +340,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_download_path_dir_radio" = "Custom folder, cleared only manually";
|
||||
"lng_download_path_choose" = "Choose download path";
|
||||
"lng_sure_clear_downloads" = "Do you want to remove all downloaded files from temp folder? It is done automatically on logout or program uninstall.";
|
||||
"lng_download_path_failed" = "File download could not be started. It could happen because of a bad download location.\n\nYou can change download path in Settings.";
|
||||
"lng_download_path_failed" = "File download could not be started.\n\nThis might be because the download location you've selected is invalid. Try changing the \"Download path\" in Settings.";
|
||||
"lng_download_path_settings" = "Settings";
|
||||
"lng_download_finish_failed" = "File download could not be finished.\n\nWould you like to try again?";
|
||||
"lng_download_path_clearing" = "Clearing...";
|
||||
@@ -404,7 +405,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_cloud_password_bad" = "Password and hint cannot be the same.";
|
||||
"lng_cloud_password_email" = "Enter recovery e-mail";
|
||||
"lng_cloud_password_bad_email" = "Incorrect e-mail, please try other.";
|
||||
"lng_cloud_password_about" = "This password will be asked when you log in on a new device in addition to the pin code.";
|
||||
"lng_cloud_password_about" = "This password will be asked when you log in on a new device in addition to the SMS code.";
|
||||
"lng_cloud_password_about_recover" = "Warning! Are you sure you don't want to\nadd a password recovery e-mail?\n\nIf you forget your password, you will\nlose access to your Telegram account.";
|
||||
"lng_cloud_password_skip_email" = "Skip e-mail";
|
||||
"lng_cloud_password_almost" = "A confirmation link was sent to the\ne-mail you provided. Two-step verification will be enabled as soon as you follow that link.";
|
||||
@@ -445,9 +446,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_settings_reset_button" = "Terminate";
|
||||
"lng_settings_manage_local_storage" = "Manage local storage";
|
||||
"lng_settings_ask_question" = "Ask a Question";
|
||||
"lng_settings_ask_sure" = "Please note that Telegram Support is done by volunteers. We try to respond as quickly as possible, but it may take a while.\n\nPlease take a look at the Telegram FAQ: it has important troubleshooting tips and answers to most questions.";
|
||||
"lng_settings_ask_sure" = "Please note that Telegram Support is run by volunteers. We try to respond as quickly as possible, but it may take a while.\n\nPlease take a look at the Telegram FAQ: it has important troubleshooting tips and answers to most questions.";
|
||||
"lng_settings_faq_button" = "Go to FAQ";
|
||||
"lng_settings_ask_ok" = "Ask";
|
||||
"lng_settings_ask_ok" = "Ask a Volunteer";
|
||||
"lng_settings_faq" = "Telegram FAQ";
|
||||
"lng_settings_logout" = "Log Out";
|
||||
"lng_sure_logout" = "Are you sure you want to log out?";
|
||||
@@ -461,7 +462,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_sessions_terminate_all" = "Terminate all other sessions";
|
||||
|
||||
"lng_blocked_list_title" = "Blocked users";
|
||||
"lng_blocked_list_unknown_phone" = "unknown phone";
|
||||
"lng_blocked_list_unknown_phone" = "unknown phone number";
|
||||
"lng_blocked_list_unblock" = "Unblock";
|
||||
"lng_blocked_list_add" = "Block user";
|
||||
"lng_blocked_list_add_title" = "Select user to block";
|
||||
@@ -499,7 +500,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_edit_privacy_groups_always_title" = "Always allow";
|
||||
"lng_edit_privacy_groups_never_title" = "Never allow";
|
||||
|
||||
"lng_edit_privacy_calls_title" = "Phone calls privacy";
|
||||
"lng_edit_privacy_calls_title" = "Telegram call privacy";
|
||||
"lng_edit_privacy_calls_description" = "You can restrict who can call you:";
|
||||
"lng_edit_privacy_calls_always_empty" = "Always allow";
|
||||
"lng_edit_privacy_calls_always#one" = "Always allow {count} user";
|
||||
@@ -577,6 +578,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_profile_kick" = "Remove";
|
||||
"lng_profile_sure_kick" = "Remove {user} from the group?";
|
||||
"lng_profile_sure_kick_channel" = "Remove {user} from the channel?";
|
||||
"lng_profile_sure_remove_admin" = "Remove {user} from admins?";
|
||||
"lng_profile_loading" = "Loading...";
|
||||
"lng_profile_photos#one" = "{count} photo";
|
||||
"lng_profile_photos#other" = "{count} photos";
|
||||
@@ -697,6 +699,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_channel_banned_status_restricted_by" = "Restricted by {user}";
|
||||
|
||||
"lng_group_blocked_list_about" = "Banned users are removed from the group and can only come back if invited by an admin.\nInvite links don't work for them.";
|
||||
"lng_channel_blocked_list_about" = "Banned users are removed from the channel.\nInvite links don't work for them.";
|
||||
|
||||
"lng_chat_all_members_admins" = "All Members Are Admins";
|
||||
"lng_chat_about_all_admins" = "Group members can add new members, edit name and photo of the group.";
|
||||
@@ -724,7 +727,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_create_channel_link_invalid" = "This link is invalid";
|
||||
"lng_create_channel_link_occupied" = "Sorry, this link is already occupied";
|
||||
"lng_create_channel_link_too_short" = "Sorry, this link is too short";
|
||||
"lng_create_channel_link_bad_symbols" = "Sorry, this link has bad symbols";
|
||||
"lng_create_channel_link_bad_symbols" = "Only 0-9, a-z, and underscores allowed.";
|
||||
"lng_create_channel_link_available" = "This link is available";
|
||||
"lng_create_channel_link_copied" = "Link copied to clipboard";
|
||||
|
||||
@@ -735,18 +738,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_failed_add_not_mutual" = "Sorry, if a person leaves a group, only a mutual contact can bring them back (they need to have your phone number, and you need theirs).";
|
||||
"lng_failed_add_not_mutual_channel" = "Sorry, if a person leaves a channel, only a mutual contact can bring them back (they need to have your phone number, and you need theirs).";
|
||||
|
||||
"lng_sure_delete_contact" = "Are you sure, you want to delete {contact} from your contact list?";
|
||||
"lng_sure_delete_history" = "Are you sure, you want to delete all message history with {contact}?\n\nThis action cannot be undone.";
|
||||
"lng_sure_delete_contact" = "Are you sure you want to delete {contact} from your contact list?";
|
||||
"lng_sure_delete_history" = "Are you sure you want to delete all message history with {contact}?\n\nThis action cannot be undone.";
|
||||
"lng_sure_delete_group_history" = "Are you sure, you want to delete all message history in «{group}»?\n\nThis action cannot be undone.";
|
||||
"lng_sure_delete_and_exit" = "Are you sure, you want to delete all message history and leave «{group}»?\n\nThis action cannot be undone.";
|
||||
"lng_sure_leave_channel" = "Are you sure, you want to leave\nthis channel?";
|
||||
"lng_sure_delete_channel" = "Are you sure, you want to delete this channel? All members will be removed and all messages will be lost.";
|
||||
"lng_sure_leave_group" = "Are you sure, you want to leave\nthis group?";
|
||||
"lng_sure_leave_channel" = "Are you sure you want to leave\nthis channel?";
|
||||
"lng_sure_delete_channel" = "Are you sure you want to delete this channel? All members will be removed and all messages will be lost.";
|
||||
"lng_sure_leave_group" = "Are you sure you want to leave this group?";
|
||||
"lng_sure_delete_group" = "Are you sure, you want to delete this group? All members will be removed and all messages will be lost.";
|
||||
"lng_sure_delete_saved_messages" = "Are you sure, you want to delete all your saved messages?\n\nThis action cannot be undone.";
|
||||
|
||||
"lng_message_empty" = "Empty Message";
|
||||
"lng_message_unsupported" = "This message is not supported by your version of Telegram Desktop. Please update to the last version in Settings or install it from {link}";
|
||||
"lng_message_unsupported" = "This message is not supported by your version of Telegram Desktop. Please update to the latest version in Settings, or install it from {link}";
|
||||
|
||||
"lng_duration_seconds#one" = "{count} second";
|
||||
"lng_duration_seconds#other" = "{count} seconds";
|
||||
@@ -813,7 +816,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_profile_migrate_reached#one" = "{count} member limit reached";
|
||||
"lng_profile_migrate_reached#other" = "{count} members limit reached";
|
||||
"lng_profile_migrate_body" = "To get over this limit, you can upgrade your group to a supergroup.";
|
||||
"lng_profile_migrate_body" = "To add more members, you can upgrade your group to a supergroup.";
|
||||
"lng_profile_migrate_learn_more" = "Learn more »";
|
||||
"lng_profile_migrate_button" = "Upgrade to supergroup";
|
||||
"lng_profile_convert_title" = "Convert to supergroup";
|
||||
@@ -860,12 +863,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_edited" = "edited";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_admin_badge" = "admin";
|
||||
"lng_fast_reply" = "Reply";
|
||||
"lng_cancel_edit_post_sure" = "Cancel editing?";
|
||||
"lng_cancel_edit_post_yes" = "Yes";
|
||||
"lng_cancel_edit_post_no" = "No";
|
||||
|
||||
"lng_bot_share_location_unavailable" = "Sorry, the location sharing is currently unavailable in Telegram Desktop.";
|
||||
"lng_bot_share_phone" = "Share Phone Number?";
|
||||
"lng_bot_share_location_unavailable" = "Sorry, location sharing is currently unavailable in Telegram Desktop.";
|
||||
"lng_bot_share_phone" = "Do you want to share your phone number with this bot?";
|
||||
"lng_bot_share_phone_confirm" = "Share";
|
||||
|
||||
"lng_attach_failed" = "Failed";
|
||||
@@ -900,7 +904,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_faved_stickers_add" = "Add to Favorites";
|
||||
"lng_faved_stickers_remove" = "Remove from Favorites";
|
||||
"lng_group_stickers" = "Group stickers";
|
||||
"lng_group_stickers_description" = "You can choose sticker set which will be available for every member while in the group chat.";
|
||||
"lng_group_stickers_description" = "You can choose a sticker set which will be available for every member while in the group chat.";
|
||||
"lng_group_stickers_add" = "Choose sticker set";
|
||||
|
||||
"lng_switch_stickers" = "Stickers";
|
||||
@@ -936,6 +940,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_stickers_group_from_featured" = "Choose from trending stickers";
|
||||
|
||||
"lng_in_dlg_photo" = "Photo";
|
||||
"lng_in_dlg_album" = "Album";
|
||||
"lng_in_dlg_video" = "Video";
|
||||
"lng_in_dlg_audio_file" = "Audio file";
|
||||
"lng_in_dlg_contact" = "Contact";
|
||||
@@ -989,7 +994,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_open_this_link" = "Open this link?";
|
||||
"lng_open_link" = "Open";
|
||||
"lng_allow_bot_pass" = "Do you allow {bot_name} to pass your Telegram name and id to the web pages you open via this bot?";
|
||||
"lng_allow_bot_pass" = "Allow {bot_name} to pass your Telegram name and ID to the web pages you open via this bot?";
|
||||
"lng_allow_bot" = "Allow";
|
||||
|
||||
"lng_bot_start" = "Start";
|
||||
@@ -998,7 +1003,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_bot_groups_not_found" = "No groups found";
|
||||
"lng_bot_sure_invite" = "Add the bot to «{group}»?";
|
||||
"lng_bot_already_in_group" = "The bot is already a member of the group.";
|
||||
"lng_bot_choose_chat" = "Select a Chat";
|
||||
"lng_bot_choose_chat" = "Select a chat";
|
||||
"lng_bot_no_chats" = "You have no chats";
|
||||
"lng_bot_chats_not_found" = "No chats found";
|
||||
"lng_bot_sure_share_game" = "Share the game with {user}?";
|
||||
@@ -1091,18 +1096,21 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_context_forward_selected" = "Forward Selected";
|
||||
"lng_context_delete_selected" = "Delete Selected";
|
||||
"lng_context_clear_selection" = "Clear Selection";
|
||||
"lng_send_images_compress#one" = "Compress image";
|
||||
"lng_send_images_compress#other" = "Compress images";
|
||||
"lng_send_image_empty" = "Could not send an empty file: {name}";
|
||||
"lng_send_image_too_large" = "Could not send a file, because it is larger than 1500 MB: {name}";
|
||||
"lng_send_images_selected#one" = "{count} image selected";
|
||||
"lng_send_images_selected#other" = "{count} images selected";
|
||||
"lng_send_photos#one" = "Send {count} photo";
|
||||
"lng_send_photos#other" = "Send {count} photos";
|
||||
"lng_send_separate_photos" = "Send as separate photos";
|
||||
"lng_send_separate_photos_videos" = "Send as separate media";
|
||||
"lng_send_files_selected#one" = "{count} file selected";
|
||||
"lng_send_files_selected#other" = "{count} files selected";
|
||||
"lng_send_files#one" = "Send {count} file";
|
||||
"lng_send_files#other" = "Send {count} files";
|
||||
"lng_send_album" = "Send as an album";
|
||||
"lng_send_photo" = "Send as a photo";
|
||||
"lng_send_file" = "Send as a file";
|
||||
|
||||
"lng_forward_choose" = "Choose recipient...";
|
||||
"lng_forward_cant" = "Sorry, no way to forward here :(";
|
||||
@@ -1122,7 +1130,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_share_cant" = "Sorry, no way to share here :(";
|
||||
"lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :(";
|
||||
"lng_reply_cant_forward" = "Sorry, no way to reply to an old message in supergroup :( Do you wish to forward it and add your comment?";
|
||||
"lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?";
|
||||
|
||||
"lng_share_title" = "Share to";
|
||||
"lng_share_copy_link" = "Copy share link";
|
||||
@@ -1131,7 +1139,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_share_game_link_copied" = "Game link copied to clipboard.";
|
||||
"lng_share_done" = "Done!";
|
||||
|
||||
"lng_contact_phone" = "Phone number";
|
||||
"lng_contact_phone" = "Phone Number";
|
||||
"lng_enter_contact_data" = "New Contact";
|
||||
"lng_edit_contact_title" = "Edit contact name";
|
||||
"lng_edit_channel_title" = "Edit channel";
|
||||
@@ -1145,8 +1153,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_add_contact" = "Create";
|
||||
"lng_add_contact_button" = "New contact";
|
||||
"lng_contacts_header" = "Contacts";
|
||||
"lng_contact_not_joined" = "Unfortunately {name} did not join Telegram yet, but you can send your friend an invitation.\n\nWe will notify you about any of your contacts who is joining Telegram.";
|
||||
"lng_try_other_contact" = "Try other";
|
||||
"lng_contact_not_joined" = "Unfortunately {name} has not joined Telegram yet, but you can send them an invitation.\n\nWe will notify you about any of your contacts who join Telegram.";
|
||||
"lng_try_other_contact" = "Try someone else";
|
||||
"lng_create_group_link" = "Link";
|
||||
"lng_create_group_invite_link" = "Invite link";
|
||||
"lng_create_group_description" = "Description (optional)";
|
||||
@@ -1233,7 +1241,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_confirm_phone_enter_code" = "Please enter the code.";
|
||||
|
||||
"lng_theme_editor_no_keys" = "No keys in the palette yet";
|
||||
"lng_theme_editor_cant_change_theme" = "You can not apply new themes while you're editing the color palette. Please close the theme editor first.";
|
||||
"lng_theme_editor_cant_change_theme" = "You can't apply a new theme while you're editing the colour palette. Please close the theme editor first.";
|
||||
"lng_theme_editor_new_keys" = "Not in the palette yet";
|
||||
"lng_theme_editor_background_image" = "Background image";
|
||||
"lng_theme_editor_saved_to_jpg" = "Saved to JPEG, {size}";
|
||||
@@ -1274,7 +1282,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_call_bar_hangup" = "End call";
|
||||
|
||||
"lng_call_box_title" = "Calls";
|
||||
"lng_call_box_about" = "You didn't make any calls yet.";
|
||||
"lng_call_box_about" = "You haven't made any Telegram calls yet.";
|
||||
"lng_call_box_status_today" = "{time}";
|
||||
"lng_call_box_status_yesterday" = "Yesterday at {time}";
|
||||
"lng_call_box_status_date" = "{date} at {time}";
|
||||
@@ -1295,7 +1303,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_player_message_yesterday" = "Yesterday at {time}";
|
||||
"lng_player_message_date" = "{date} at {time}";
|
||||
|
||||
"lng_rights_edit_admin" = "Edit administrator";
|
||||
"lng_rights_edit_admin" = "Manage permissions";
|
||||
"lng_rights_edit_admin_header" = "What can this admin do?";
|
||||
"lng_rights_about_add_admins_yes" = "This admin will be able to add new admins with the same (or more limited) permissions.";
|
||||
"lng_rights_about_add_admins_no" = "This admin will not be able to add new admins.";
|
||||
@@ -1399,9 +1407,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_admin_log_banned" = "banned {user}";
|
||||
"lng_admin_log_restricted" = "changed restrictions for {user} {until}";
|
||||
"lng_admin_log_promoted" = "changed privileges for {user}";
|
||||
"lng_admin_log_changed_stickers_group" = "{from} changed group {sticker_set}";
|
||||
"lng_admin_log_changed_stickers_group" = "{from} changed the group's {sticker_set}";
|
||||
"lng_admin_log_changed_stickers_set" = "sticker set";
|
||||
"lng_admin_log_removed_stickers_group" = "{from} removed group sticker set";
|
||||
"lng_admin_log_removed_stickers_group" = "{from} removed the group's sticker set";
|
||||
"lng_admin_log_user_with_username" = "{name} ({mention})";
|
||||
"lng_admin_log_restricted_forever" = "indefinitely";
|
||||
"lng_admin_log_restricted_until" = "until {date}";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="1.2.0.0" />
|
||||
Version="1.2.7.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,2,0,0
|
||||
PRODUCTVERSION 1,2,0,0
|
||||
FILEVERSION 1,2,7,0
|
||||
PRODUCTVERSION 1,2,7,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -52,10 +52,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "1.2.0.0"
|
||||
VALUE "FileVersion", "1.2.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "1.2.0.0"
|
||||
VALUE "ProductVersion", "1.2.7.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,2,0,0
|
||||
PRODUCTVERSION 1,2,0,0
|
||||
FILEVERSION 1,2,7,0
|
||||
PRODUCTVERSION 1,2,7,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -43,10 +43,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "1.2.0.0"
|
||||
VALUE "FileVersion", "1.2.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "1.2.0.0"
|
||||
VALUE "ProductVersion", "1.2.7.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -22,7 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
bool _debug = false;
|
||||
|
||||
wstring updaterName, updaterDir, updateTo, exeName;
|
||||
wstring updaterName, updaterDir, updateTo, exeName, customWorkingDir, customKeyFile;
|
||||
|
||||
bool equal(const wstring &a, const wstring &b) {
|
||||
return !_wcsicmp(a.c_str(), b.c_str());
|
||||
@@ -356,6 +356,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
|
||||
args = CommandLineToArgvW(GetCommandLine(), &argsCount);
|
||||
if (args) {
|
||||
for (int i = 1; i < argsCount; ++i) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
if (equal(args[i], L"-update")) {
|
||||
needupdate = true;
|
||||
} else if (equal(args[i], L"-autostart")) {
|
||||
@@ -368,17 +369,25 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
|
||||
} else if (equal(args[i], L"-testmode")) {
|
||||
testmode = true;
|
||||
} else if (equal(args[i], L"-writeprotected") && ++i < argsCount) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
writeprotected = true;
|
||||
updateTo = args[i];
|
||||
for (int i = 0, l = updateTo.size(); i < l; ++i) {
|
||||
if (updateTo[i] == L'/') {
|
||||
updateTo[i] = L'\\';
|
||||
for (int j = 0, l = updateTo.size(); j < l; ++j) {
|
||||
if (updateTo[j] == L'/') {
|
||||
updateTo[j] = L'\\';
|
||||
}
|
||||
}
|
||||
} else if (equal(args[i], L"-workdir") && ++i < argsCount) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
customWorkingDir = args[i];
|
||||
} else if (equal(args[i], L"-key") && ++i < argsCount) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
customKeyFile = args[i];
|
||||
} else if (equal(args[i], L"-exename") && ++i < argsCount) {
|
||||
writeLog(std::wstring(L"Argument: ") + args[i]);
|
||||
exeName = args[i];
|
||||
for (int i = 0, l = exeName.size(); i < l; ++i) {
|
||||
if (exeName[i] == L'/' || exeName[i] == L'\\') {
|
||||
for (int j = 0, l = exeName.size(); j < l; ++j) {
|
||||
if (exeName[j] == L'/' || exeName[j] == L'\\') {
|
||||
exeName = L"Telegram.exe";
|
||||
break;
|
||||
}
|
||||
@@ -391,6 +400,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
|
||||
if (needupdate) writeLog(L"Need to update!");
|
||||
if (autostart) writeLog(L"From autostart!");
|
||||
if (writeprotected) writeLog(L"Write Protected folder!");
|
||||
if (!customWorkingDir.empty()) writeLog(L"Will pass custom working dir: " + customWorkingDir);
|
||||
|
||||
updaterName = args[0];
|
||||
writeLog(L"Updater name is: " + updaterName);
|
||||
@@ -428,6 +438,13 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
|
||||
if (debug) targs += L" -debug";
|
||||
if (startintray) targs += L" -startintray";
|
||||
if (testmode) targs += L" -testmode";
|
||||
if (!customWorkingDir.empty()) {
|
||||
targs += L" -workdir \"" + customWorkingDir + L"\"";
|
||||
}
|
||||
if (!customKeyFile.empty()) {
|
||||
targs += L" -key \"" + customKeyFile + L"\"";
|
||||
}
|
||||
writeLog(L"Result arguments: " + targs);
|
||||
|
||||
bool executed = false;
|
||||
if (writeprotected) { // run un-elevated
|
||||
|
||||
@@ -344,9 +344,16 @@ string CurrentExecutablePath(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool needupdate = true, autostart = false, debug = false, tosettings = false, startintray = false, testmode = false;
|
||||
bool needupdate = true;
|
||||
bool autostart = false;
|
||||
bool debug = false;
|
||||
bool tosettings = false;
|
||||
bool startintray = false;
|
||||
bool testmode = false;
|
||||
bool customWorkingDir = false;
|
||||
|
||||
char *key = 0, *crashreport = 0;
|
||||
char *key = 0;
|
||||
char *workdir = 0;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (equal(argv[i], "-noupdate")) {
|
||||
needupdate = false;
|
||||
@@ -360,12 +367,12 @@ int main(int argc, char *argv[]) {
|
||||
testmode = true;
|
||||
} else if (equal(argv[i], "-tosettings")) {
|
||||
tosettings = true;
|
||||
} else if (equal(argv[i], "-workdir_custom")) {
|
||||
customWorkingDir = true;
|
||||
} else if (equal(argv[i], "-key") && ++i < argc) {
|
||||
key = argv[i];
|
||||
} else if (equal(argv[i], "-workpath") && ++i < argc) {
|
||||
workDir = argv[i];
|
||||
} else if (equal(argv[i], "-crashreport") && ++i < argc) {
|
||||
crashreport = argv[i];
|
||||
workDir = workdir = argv[i];
|
||||
} else if (equal(argv[i], "-exename") && ++i < argc) {
|
||||
exeName = argv[i];
|
||||
} else if (equal(argv[i], "-exepath") && ++i < argc) {
|
||||
@@ -401,6 +408,8 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
if (needupdate) {
|
||||
if (workDir.empty()) { // old app launched, update prepared in tupdates/ready (not in tupdates/temp)
|
||||
customWorkingDir = false;
|
||||
|
||||
writeLog("No workdir, trying to figure it out");
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
if (pw && pw->pw_dir && strlen(pw->pw_dir)) {
|
||||
@@ -446,22 +455,30 @@ int main(int argc, char *argv[]) {
|
||||
string fullBinaryPath = exePath + exeName;
|
||||
strcpy(path, fullBinaryPath.c_str());
|
||||
|
||||
char *args[MaxArgsCount] = {0}, p_noupdate[] = "-noupdate", p_autostart[] = "-autostart", p_debug[] = "-debug", p_tosettings[] = "-tosettings", p_key[] = "-key", p_startintray[] = "-startintray", p_testmode[] = "-testmode";
|
||||
char *args[MaxArgsCount] = { 0 };
|
||||
char p_noupdate[] = "-noupdate";
|
||||
char p_autostart[] = "-autostart";
|
||||
char p_debug[] = "-debug";
|
||||
char p_tosettings[] = "-tosettings";
|
||||
char p_key[] = "-key";
|
||||
char p_startintray[] = "-startintray";
|
||||
char p_testmode[] = "-testmode";
|
||||
char p_workdir[] = "-workdir";
|
||||
int argIndex = 0;
|
||||
args[argIndex++] = path;
|
||||
if (crashreport) {
|
||||
args[argIndex++] = crashreport;
|
||||
} else {
|
||||
args[argIndex++] = p_noupdate;
|
||||
if (autostart) args[argIndex++] = p_autostart;
|
||||
if (debug) args[argIndex++] = p_debug;
|
||||
if (startintray) args[argIndex++] = p_startintray;
|
||||
if (testmode) args[argIndex++] = p_testmode;
|
||||
if (tosettings) args[argIndex++] = p_tosettings;
|
||||
if (key) {
|
||||
args[argIndex++] = p_key;
|
||||
args[argIndex++] = key;
|
||||
}
|
||||
args[argIndex++] = p_noupdate;
|
||||
if (autostart) args[argIndex++] = p_autostart;
|
||||
if (debug) args[argIndex++] = p_debug;
|
||||
if (startintray) args[argIndex++] = p_startintray;
|
||||
if (testmode) args[argIndex++] = p_testmode;
|
||||
if (tosettings) args[argIndex++] = p_tosettings;
|
||||
if (key) {
|
||||
args[argIndex++] = p_key;
|
||||
args[argIndex++] = key;
|
||||
}
|
||||
if (customWorkingDir && workdir) {
|
||||
args[argIndex++] = p_workdir;
|
||||
args[argIndex++] = workdir;
|
||||
}
|
||||
pid_t pid = fork();
|
||||
switch (pid) {
|
||||
|
||||
@@ -23,7 +23,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
NSString *appName = @"Telegram.app";
|
||||
NSString *appDir = nil;
|
||||
NSString *workDir = nil;
|
||||
NSString *crashReportArg = nil;
|
||||
|
||||
#ifdef _DEBUG
|
||||
BOOL _debug = YES;
|
||||
@@ -90,6 +89,7 @@ int main(int argc, const char * argv[]) {
|
||||
openLog();
|
||||
pid_t procId = 0;
|
||||
BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO, testMode = NO;
|
||||
BOOL customWorkingDir = NO;
|
||||
NSString *key = nil;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
if ([@"-workpath" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
@@ -102,10 +102,6 @@ int main(int argc, const char * argv[]) {
|
||||
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
|
||||
procId = [[formatter numberFromString:[NSString stringWithUTF8String:argv[i]]] intValue];
|
||||
}
|
||||
} else if ([@"-crashreport" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
if (++i < argc) {
|
||||
crashReportArg = [NSString stringWithUTF8String:argv[i]];
|
||||
}
|
||||
} else if ([@"-noupdate" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
update = NO;
|
||||
} else if ([@"-tosettings" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
@@ -118,11 +114,16 @@ int main(int argc, const char * argv[]) {
|
||||
startInTray = YES;
|
||||
} else if ([@"-testmode" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
testMode = YES;
|
||||
} else if ([@"-workdir_custom" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
customWorkingDir = YES;
|
||||
} else if ([@"-key" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
if (++i < argc) key = [NSString stringWithUTF8String:argv[i]];
|
||||
}
|
||||
}
|
||||
if (!workDir) workDir = appDir;
|
||||
if (!workDir) {
|
||||
workDir = appDir;
|
||||
customWorkingDir = NO;
|
||||
}
|
||||
openLog();
|
||||
NSMutableArray *argsArr = [[NSMutableArray alloc] initWithCapacity:argc];
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
@@ -242,17 +243,19 @@ int main(int argc, const char * argv[]) {
|
||||
}
|
||||
|
||||
NSString *appPath = [[NSArray arrayWithObjects:appDir, appRealName, nil] componentsJoinedByString:@""];
|
||||
NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: crashReportArg ? crashReportArg : @"-noupdate", nil];
|
||||
if (!crashReportArg) {
|
||||
if (toSettings) [args addObject:@"-tosettings"];
|
||||
if (_debug) [args addObject:@"-debug"];
|
||||
if (startInTray) [args addObject:@"-startintray"];
|
||||
if (testMode) [args addObject:@"-testmode"];
|
||||
if (autoStart) [args addObject:@"-autostart"];
|
||||
if (key) {
|
||||
[args addObject:@"-key"];
|
||||
[args addObject:key];
|
||||
}
|
||||
NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: @"-noupdate", nil];
|
||||
if (toSettings) [args addObject:@"-tosettings"];
|
||||
if (_debug) [args addObject:@"-debug"];
|
||||
if (startInTray) [args addObject:@"-startintray"];
|
||||
if (testMode) [args addObject:@"-testmode"];
|
||||
if (autoStart) [args addObject:@"-autostart"];
|
||||
if (key) {
|
||||
[args addObject:@"-key"];
|
||||
[args addObject:key];
|
||||
}
|
||||
if (customWorkingDir) {
|
||||
[args addObject:@"-workdir"];
|
||||
[args addObject:workDir];
|
||||
}
|
||||
writeLog([[NSArray arrayWithObjects:@"Running application '", appPath, @"' with args '", [args componentsJoinedByString:@"' '"], @"'..", nil] componentsJoinedByString:@""]);
|
||||
NSError *error = nil;
|
||||
|
||||
@@ -33,16 +33,19 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "mainwidget.h"
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "history/history_message.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "auth_session.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "storage/storage_user_photos.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "data/data_sparse_ids.h"
|
||||
#include "data/data_search_controller.h"
|
||||
#include "data/data_channel_admins.h"
|
||||
@@ -58,6 +61,76 @@ constexpr auto kUnreadMentionsFirstRequestLimit = 10;
|
||||
constexpr auto kUnreadMentionsNextRequestLimit = 100;
|
||||
constexpr auto kSharedMediaLimit = 100;
|
||||
constexpr auto kReadFeaturedSetsTimeout = TimeMs(1000);
|
||||
constexpr auto kFileLoaderQueueStopTimeout = TimeMs(5000);
|
||||
|
||||
bool IsSilentPost(not_null<HistoryItem*> item, bool silent) {
|
||||
const auto history = item->history();
|
||||
return silent
|
||||
&& history->peer->isChannel()
|
||||
&& !history->peer->isMegagroup();
|
||||
}
|
||||
|
||||
MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
|
||||
not_null<DocumentData*> document) {
|
||||
const auto filenameAttribute = MTP_documentAttributeFilename(
|
||||
MTP_string(document->filename()));
|
||||
const auto dimensions = document->dimensions;
|
||||
auto attributes = QVector<MTPDocumentAttribute>(1, filenameAttribute);
|
||||
if (dimensions.width() > 0 && dimensions.height() > 0) {
|
||||
const auto duration = document->duration();
|
||||
if (duration >= 0) {
|
||||
auto flags = MTPDdocumentAttributeVideo::Flags(0);
|
||||
if (document->isVideoMessage()) {
|
||||
flags |= MTPDdocumentAttributeVideo::Flag::f_round_message;
|
||||
}
|
||||
attributes.push_back(MTP_documentAttributeVideo(
|
||||
MTP_flags(flags),
|
||||
MTP_int(duration),
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height())));
|
||||
} else {
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height())));
|
||||
}
|
||||
}
|
||||
if (document->type == AnimatedDocument) {
|
||||
attributes.push_back(MTP_documentAttributeAnimated());
|
||||
} else if (document->type == StickerDocument && document->sticker()) {
|
||||
attributes.push_back(MTP_documentAttributeSticker(
|
||||
MTP_flags(0),
|
||||
MTP_string(document->sticker()->alt),
|
||||
document->sticker()->set,
|
||||
MTPMaskCoords()));
|
||||
} else if (const auto song = document->song()) {
|
||||
const auto flags = MTPDdocumentAttributeAudio::Flag::f_title
|
||||
| MTPDdocumentAttributeAudio::Flag::f_performer;
|
||||
attributes.push_back(MTP_documentAttributeAudio(
|
||||
MTP_flags(flags),
|
||||
MTP_int(song->duration),
|
||||
MTP_string(song->title),
|
||||
MTP_string(song->performer),
|
||||
MTPstring()));
|
||||
} else if (const auto voice = document->voice()) {
|
||||
const auto flags = MTPDdocumentAttributeAudio::Flag::f_voice
|
||||
| MTPDdocumentAttributeAudio::Flag::f_waveform;
|
||||
attributes.push_back(MTP_documentAttributeAudio(
|
||||
MTP_flags(flags),
|
||||
MTP_int(voice->duration),
|
||||
MTPstring(),
|
||||
MTPstring(),
|
||||
MTP_bytes(documentWaveformEncode5bit(voice->waveform))));
|
||||
}
|
||||
return MTP_vector<MTPDocumentAttribute>(attributes);
|
||||
}
|
||||
|
||||
FileLoadTo FileLoadTaskOptions(const ApiWrap::SendOptions &options) {
|
||||
const auto peer = options.history->peer;
|
||||
return FileLoadTo(
|
||||
peer->id,
|
||||
peer->notifySilentPosts(),
|
||||
options.replyTo);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -66,71 +139,40 @@ ApiWrap::ApiWrap(not_null<AuthSession*> session)
|
||||
, _messageDataResolveDelayed([this] { resolveMessageDatas(); })
|
||||
, _webPagesTimer([this] { resolveWebPages(); })
|
||||
, _draftsSaveTimer([this] { saveDraftsToCloud(); })
|
||||
, _featuredSetsReadTimer([this] { readFeaturedSets(); }) {
|
||||
, _featuredSetsReadTimer([this] { readFeaturedSets(); })
|
||||
, _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout)) {
|
||||
}
|
||||
|
||||
void ApiWrap::start() {
|
||||
Window::Theme::Background()->start();
|
||||
requestAppChangelogs();
|
||||
void ApiWrap::requestChangelog(
|
||||
const QString &sinceVersion,
|
||||
base::lambda<void(const MTPUpdates &result)> callback) {
|
||||
request(MTPhelp_GetAppChangelog(
|
||||
MTP_string(sinceVersion)
|
||||
)).done(
|
||||
callback
|
||||
).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestAppChangelogs() {
|
||||
auto oldAppVersion = Local::oldMapVersion();
|
||||
if (oldAppVersion > 0 && oldAppVersion < AppVersion) {
|
||||
_changelogSubscription = subscribe(_session->data().moreChatsLoaded(), [this, oldAppVersion] {
|
||||
auto oldVersionString = qsl("%1.%2.%3").arg(oldAppVersion / 1000000).arg((oldAppVersion % 1000000) / 1000).arg(oldAppVersion % 1000);
|
||||
request(MTPhelp_GetAppChangelog(MTP_string(oldVersionString))).done([this, oldAppVersion](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
auto resultEmpty = true;
|
||||
switch (result.type()) {
|
||||
case mtpc_updateShortMessage:
|
||||
case mtpc_updateShortChatMessage:
|
||||
case mtpc_updateShort: resultEmpty = false; break;
|
||||
case mtpc_updatesCombined: resultEmpty = result.c_updatesCombined().vupdates.v.isEmpty(); break;
|
||||
case mtpc_updates: resultEmpty = result.c_updates().vupdates.v.isEmpty(); break;
|
||||
case mtpc_updatesTooLong:
|
||||
case mtpc_updateShortSentMessage: LOG(("API Error: Bad updates type in app changelog.")); break;
|
||||
}
|
||||
if (resultEmpty) {
|
||||
addLocalChangelogs(oldAppVersion);
|
||||
}
|
||||
}).send();
|
||||
unsubscribe(base::take(_changelogSubscription));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::addLocalChangelogs(int oldAppVersion) {
|
||||
auto addedSome = false;
|
||||
auto addLocalChangelog = [this, &addedSome](const QString &text) {
|
||||
auto textWithEntities = TextWithEntities { text };
|
||||
TextUtilities::ParseEntities(textWithEntities, TextParseLinks);
|
||||
App::wnd()->serviceNotification(textWithEntities, MTP_messageMediaEmpty(), unixtime());
|
||||
addedSome = true;
|
||||
};
|
||||
if (cAlphaVersion() || cBetaVersion()) {
|
||||
auto addLocalAlphaChangelog = [this, oldAppVersion, addLocalChangelog](int changeVersion, const char *changes) {
|
||||
if (oldAppVersion < changeVersion) {
|
||||
auto changeVersionString = QString::number(changeVersion / 1000000) + '.' + QString::number((changeVersion % 1000000) / 1000) + ((changeVersion % 1000) ? ('.' + QString::number(changeVersion % 1000)) : QString());
|
||||
auto text = qsl("New in version %1:\n\n").arg(changeVersionString) + QString::fromUtf8(changes).trimmed();
|
||||
addLocalChangelog(text);
|
||||
}
|
||||
};
|
||||
addLocalAlphaChangelog(1001024, "\xE2\x80\x94 Radically improved navigation. New side panel on the right with quick access to shared media and group members.\n\xE2\x80\x94 Pinned Messages. If you are a channel admin, pin messages to focus your subscribers\xE2\x80\x99 attention on important announcements.\n\xE2\x80\x94 Also supported clearing history in supergroups and added a host of minor improvements.");
|
||||
addLocalAlphaChangelog(1001026, "\xE2\x80\x94 Admin badges in supergroup messages.\n\xE2\x80\x94 Fix crashing on launch in OS X 10.6.\n\xE2\x80\x94 Bug fixes and other minor improvements.");
|
||||
addLocalAlphaChangelog(1001027, "\xE2\x80\x94 Saved Messages. Bookmark messages by forwarding them to \xE2\x80\x9C""Saved Messages\xE2\x80\x9D. Access them from the Chats list or from the side menu.");
|
||||
}
|
||||
if (!addedSome) {
|
||||
auto text = lng_new_version_wrap(lt_version, str_const_toString(AppVersionStr), lt_changes, lang(lng_new_version_minor), lt_link, qsl("https://desktop.telegram.org/changelog")).trimmed();
|
||||
addLocalChangelog(text);
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId) {
|
||||
void ApiWrap::applyUpdates(
|
||||
const MTPUpdates &updates,
|
||||
uint64 sentMessageRandomId) {
|
||||
App::main()->feedUpdates(updates, sentMessageRandomId);
|
||||
}
|
||||
|
||||
void ApiWrap::sendMessageFail(const RPCError &error) {
|
||||
if (error.type() == qstr("PEER_FLOOD")) {
|
||||
Ui::show(Box<InformBox>(
|
||||
PeerFloodErrorText(PeerFloodType::Send)));
|
||||
} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
|
||||
const auto link = textcmdLink(
|
||||
Messenger::Instance().createInternalLinkFull(qsl("spambot")),
|
||||
lang(lng_cant_more_info));
|
||||
Ui::show(Box<InformBox>(lng_error_public_groups_denied(
|
||||
lt_more_info,
|
||||
link)));
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback) {
|
||||
auto &req = (channel ? _channelMessageDataRequests[channel][msgId] : _messageDataRequests[msgId]);
|
||||
if (callback) {
|
||||
@@ -845,47 +887,65 @@ void ApiWrap::requestSelfParticipant(ChannelData *channel) {
|
||||
_selfParticipantRequests.insert(channel, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::kickParticipant(PeerData *peer, UserData *user, const MTPChannelBannedRights ¤tRights) {
|
||||
auto kick = KickRequest(peer, user);
|
||||
if (_kickRequests.contains(kick)) return;
|
||||
|
||||
if (auto channel = peer->asChannel()) {
|
||||
auto rights = ChannelData::KickedRestrictedRights();
|
||||
auto requestId = request(MTPchannels_EditBanned(channel->inputChannel, user->inputUser, rights)).done([this, channel, user, currentRights, rights](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
_kickRequests.remove(KickRequest(channel, user));
|
||||
channel->applyEditBanned(user, currentRights, rights);
|
||||
}).fail([this, kick](const RPCError &error) {
|
||||
_kickRequests.remove(kick);
|
||||
}).send();
|
||||
|
||||
_kickRequests.insert(kick, requestId);
|
||||
}
|
||||
void ApiWrap::kickParticipant(
|
||||
not_null<ChatData*> chat,
|
||||
not_null<UserData*> user) {
|
||||
request(MTPmessages_DeleteChatUser(
|
||||
chat->inputChat,
|
||||
user->inputUser
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::unblockParticipant(PeerData *peer, UserData *user) {
|
||||
auto kick = KickRequest(peer, user);
|
||||
void ApiWrap::kickParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user,
|
||||
const MTPChannelBannedRights ¤tRights) {
|
||||
const auto kick = KickRequest(channel, user);
|
||||
if (_kickRequests.contains(kick)) return;
|
||||
|
||||
if (auto channel = peer->asChannel()) {
|
||||
auto requestId = request(MTPchannels_EditBanned(channel->inputChannel, user->inputUser, MTP_channelBannedRights(MTP_flags(0), MTP_int(0)))).done([this, peer, user](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
const auto rights = ChannelData::KickedRestrictedRights();
|
||||
const auto requestId = request(MTPchannels_EditBanned(
|
||||
channel->inputChannel,
|
||||
user->inputUser,
|
||||
rights
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
_kickRequests.remove(KickRequest(peer, user));
|
||||
if (auto channel = peer->asMegagroup()) {
|
||||
if (channel->kickedCount() > 0) {
|
||||
channel->setKickedCount(channel->kickedCount() - 1);
|
||||
} else {
|
||||
channel->updateFullForced();
|
||||
}
|
||||
}
|
||||
}).fail([this, kick](const RPCError &error) {
|
||||
_kickRequests.remove(kick);
|
||||
}).send();
|
||||
_kickRequests.remove(KickRequest(channel, user));
|
||||
channel->applyEditBanned(user, currentRights, rights);
|
||||
}).fail([this, kick](const RPCError &error) {
|
||||
_kickRequests.remove(kick);
|
||||
}).send();
|
||||
|
||||
_kickRequests.insert(kick, requestId);
|
||||
}
|
||||
_kickRequests.emplace(kick, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::unblockParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user) {
|
||||
const auto kick = KickRequest(channel, user);
|
||||
if (_kickRequests.contains(kick)) return;
|
||||
|
||||
const auto requestId = request(MTPchannels_EditBanned(
|
||||
channel->inputChannel,
|
||||
user->inputUser,
|
||||
MTP_channelBannedRights(MTP_flags(0), MTP_int(0))
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
_kickRequests.remove(KickRequest(channel, user));
|
||||
if (channel->kickedCount() > 0) {
|
||||
channel->setKickedCount(channel->kickedCount() - 1);
|
||||
} else {
|
||||
channel->updateFullForced();
|
||||
}
|
||||
}).fail([this, kick](const RPCError &error) {
|
||||
_kickRequests.remove(kick);
|
||||
}).send();
|
||||
|
||||
_kickRequests.emplace(kick, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::requestChannelMembersForAdd(
|
||||
@@ -946,7 +1006,7 @@ void ApiWrap::saveStickerSets(const Stickers::Order &localOrder, const Stickers:
|
||||
request(base::take(_stickersClearRecentRequestId)).cancel();
|
||||
|
||||
auto writeInstalled = true, writeRecent = false, writeCloudRecent = false, writeFaved = false, writeArchived = false;
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = Stickers::GetRecentPack();
|
||||
auto &sets = Auth().data().stickerSetsRef();
|
||||
|
||||
_stickersOrder = localOrder;
|
||||
@@ -1608,18 +1668,18 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
|
||||
}
|
||||
|
||||
if (!v) return;
|
||||
QMap<uint64, int32> msgsIds; // copied from feedMsgs
|
||||
for (int32 i = 0, l = v->size(); i < l; ++i) {
|
||||
const auto &msg(v->at(i));
|
||||
switch (msg.type()) {
|
||||
case mtpc_message: msgsIds.insert((uint64(uint32(msg.c_message().vid.v)) << 32) | uint64(i), i); break;
|
||||
case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i); break;
|
||||
case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i); break;
|
||||
}
|
||||
|
||||
auto indices = base::flat_map<uint64, int>(); // copied from feedMsgs
|
||||
for (auto i = 0, l = v->size(); i != l; ++i) {
|
||||
const auto msgId = idFromMessage(v->at(i));
|
||||
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
|
||||
}
|
||||
|
||||
for_const (auto msgId, msgsIds) {
|
||||
if (auto item = App::histories().addNewMessage(v->at(msgId), NewMessageExisting)) {
|
||||
for (const auto [position, index] : indices) {
|
||||
const auto item = App::histories().addNewMessage(
|
||||
v->at(index),
|
||||
NewMessageExisting);
|
||||
if (item) {
|
||||
item->setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
@@ -1897,45 +1957,49 @@ void ApiWrap::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
case mtpc_updateShortMessage: {
|
||||
auto &d = updates.c_updateShortMessage();
|
||||
auto flags = mtpCastFlags(d.vflags.v) | MTPDmessage::Flag::f_from_id;
|
||||
App::histories().addNewMessage(MTP_message(
|
||||
MTP_flags(flags),
|
||||
d.vid,
|
||||
d.is_out() ? MTP_int(Auth().userId()) : d.vuser_id,
|
||||
MTP_peerUser(d.is_out() ? d.vuser_id : MTP_int(Auth().userId())),
|
||||
d.vfwd_from,
|
||||
d.vvia_bot_id,
|
||||
d.vreply_to_msg_id,
|
||||
d.vdate,
|
||||
d.vmessage,
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPnullMarkup,
|
||||
d.has_entities() ? d.ventities : MTPnullEntities,
|
||||
MTPint(),
|
||||
MTPint(),
|
||||
MTPstring(),
|
||||
MTPlong()), NewMessageUnread);
|
||||
App::histories().addNewMessage(
|
||||
MTP_message(
|
||||
MTP_flags(flags),
|
||||
d.vid,
|
||||
d.is_out() ? MTP_int(Auth().userId()) : d.vuser_id,
|
||||
MTP_peerUser(d.is_out() ? d.vuser_id : MTP_int(Auth().userId())),
|
||||
d.vfwd_from,
|
||||
d.vvia_bot_id,
|
||||
d.vreply_to_msg_id,
|
||||
d.vdate,
|
||||
d.vmessage,
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPnullMarkup,
|
||||
d.has_entities() ? d.ventities : MTPnullEntities,
|
||||
MTPint(),
|
||||
MTPint(),
|
||||
MTPstring(),
|
||||
MTPlong()),
|
||||
NewMessageUnread);
|
||||
} break;
|
||||
|
||||
case mtpc_updateShortChatMessage: {
|
||||
auto &d = updates.c_updateShortChatMessage();
|
||||
auto flags = mtpCastFlags(d.vflags.v) | MTPDmessage::Flag::f_from_id;
|
||||
App::histories().addNewMessage(MTP_message(
|
||||
MTP_flags(flags),
|
||||
d.vid,
|
||||
d.vfrom_id,
|
||||
MTP_peerChat(d.vchat_id),
|
||||
d.vfwd_from,
|
||||
d.vvia_bot_id,
|
||||
d.vreply_to_msg_id,
|
||||
d.vdate,
|
||||
d.vmessage,
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPnullMarkup,
|
||||
d.has_entities() ? d.ventities : MTPnullEntities,
|
||||
MTPint(),
|
||||
MTPint(),
|
||||
MTPstring(),
|
||||
MTPlong()), NewMessageUnread);
|
||||
App::histories().addNewMessage(
|
||||
MTP_message(
|
||||
MTP_flags(flags),
|
||||
d.vid,
|
||||
d.vfrom_id,
|
||||
MTP_peerChat(d.vchat_id),
|
||||
d.vfwd_from,
|
||||
d.vvia_bot_id,
|
||||
d.vreply_to_msg_id,
|
||||
d.vdate,
|
||||
d.vmessage,
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPnullMarkup,
|
||||
d.has_entities() ? d.ventities : MTPnullEntities,
|
||||
MTPint(),
|
||||
MTPint(),
|
||||
MTPstring(),
|
||||
MTPlong()),
|
||||
NewMessageUnread);
|
||||
} break;
|
||||
|
||||
case mtpc_updateShortSentMessage: {
|
||||
@@ -2455,6 +2519,7 @@ void ApiWrap::forwardMessages(
|
||||
}
|
||||
|
||||
auto forwardFrom = items.front()->history()->peer;
|
||||
auto currentGroupId = items.front()->groupId();
|
||||
auto ids = QVector<MTPint>();
|
||||
auto randomIds = QVector<MTPlong>();
|
||||
|
||||
@@ -2462,8 +2527,12 @@ void ApiWrap::forwardMessages(
|
||||
if (shared) {
|
||||
++shared->requestsLeft;
|
||||
}
|
||||
const auto finalFlags = sendFlags
|
||||
| (currentGroupId == MessageGroupId()
|
||||
? MTPmessages_ForwardMessages::Flag(0)
|
||||
: MTPmessages_ForwardMessages::Flag::f_grouped);
|
||||
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
MTP_flags(finalFlags),
|
||||
forwardFrom->input,
|
||||
MTP_vector<MTPint>(ids),
|
||||
MTP_vector<MTPlong>(randomIds),
|
||||
@@ -2508,9 +2577,13 @@ void ApiWrap::forwardMessages(
|
||||
App::historyRegRandom(randomId, newId);
|
||||
}
|
||||
}
|
||||
if (forwardFrom != item->history()->peer) {
|
||||
const auto newFrom = item->history()->peer;
|
||||
const auto newGroupId = item->groupId();
|
||||
if (forwardFrom != newFrom
|
||||
|| currentGroupId != newGroupId) {
|
||||
sendAccumulated();
|
||||
forwardFrom = item->history()->peer;
|
||||
forwardFrom = newFrom;
|
||||
currentGroupId = newGroupId;
|
||||
}
|
||||
ids.push_back(MTP_int(item->id));
|
||||
randomIds.push_back(MTP_long(randomId));
|
||||
@@ -2556,17 +2629,12 @@ void ApiWrap::sendSharedContact(
|
||||
const auto history = options.history;
|
||||
const auto peer = history->peer;
|
||||
|
||||
const auto randomId = rand_value<uint64>();
|
||||
const auto newId = FullMsgId(history->channelId(), clientMsgId());
|
||||
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
|
||||
const auto silentPost = channelPost && peer->notifySilentPosts();
|
||||
|
||||
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
|
||||
|
||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||
if (options.replyTo) {
|
||||
flags |= MTPDmessage::Flag::f_reply_to_msg_id;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
|
||||
}
|
||||
if (channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_views;
|
||||
@@ -2577,14 +2645,11 @@ void ApiWrap::sendSharedContact(
|
||||
} else {
|
||||
flags |= MTPDmessage::Flag::f_from_id;
|
||||
}
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
const auto messageFromId = channelPost ? 0 : Auth().userId();
|
||||
const auto messagePostAuthor = channelPost
|
||||
? (Auth().user()->firstName + ' ' + Auth().user()->lastName)
|
||||
: QString();
|
||||
history->addNewMessage(
|
||||
const auto item = history->addNewMessage(
|
||||
MTP_message(
|
||||
MTP_flags(flags),
|
||||
MTP_int(newId.msg),
|
||||
@@ -2608,35 +2673,358 @@ void ApiWrap::sendSharedContact(
|
||||
MTPlong()),
|
||||
NewMessageUnread);
|
||||
|
||||
const auto media = MTP_inputMediaContact(
|
||||
MTP_string(phone),
|
||||
MTP_string(firstName),
|
||||
MTP_string(lastName));
|
||||
sendMedia(item, media, peer->notifySilentPosts());
|
||||
}
|
||||
|
||||
void ApiWrap::sendVoiceMessage(
|
||||
QByteArray result,
|
||||
VoiceWaveform waveform,
|
||||
int duration,
|
||||
const SendOptions &options) {
|
||||
const auto caption = QString();
|
||||
const auto to = FileLoadTaskOptions(options);
|
||||
_fileLoader->addTask(std::make_unique<FileLoadTask>(
|
||||
result,
|
||||
duration,
|
||||
waveform,
|
||||
to,
|
||||
caption));
|
||||
}
|
||||
|
||||
void ApiWrap::sendFiles(
|
||||
Storage::PreparedList &&list,
|
||||
SendMediaType type,
|
||||
QString caption,
|
||||
std::shared_ptr<SendingAlbum> album,
|
||||
const SendOptions &options) {
|
||||
if (list.files.size() > 1 && !caption.isEmpty()) {
|
||||
auto message = MainWidget::MessageToSend(options.history);
|
||||
message.textWithTags = { caption, TextWithTags::Tags() };
|
||||
message.replyTo = options.replyTo;
|
||||
message.clearDraft = false;
|
||||
App::main()->sendMessage(message);
|
||||
caption = QString();
|
||||
}
|
||||
|
||||
const auto to = FileLoadTaskOptions(options);
|
||||
if (album) {
|
||||
album->silent = to.silent;
|
||||
}
|
||||
auto tasks = std::vector<std::unique_ptr<Task>>();
|
||||
tasks.reserve(list.files.size());
|
||||
for (auto &file : list.files) {
|
||||
if (album) {
|
||||
switch (file.type) {
|
||||
case Storage::PreparedFile::AlbumType::Photo:
|
||||
type = SendMediaType::Photo;
|
||||
break;
|
||||
case Storage::PreparedFile::AlbumType::Video:
|
||||
type = SendMediaType::File;
|
||||
break;
|
||||
default: Unexpected("AlbumType in uploadFilesAfterConfirmation");
|
||||
}
|
||||
}
|
||||
tasks.push_back(std::make_unique<FileLoadTask>(
|
||||
file.path,
|
||||
file.content,
|
||||
std::move(file.information),
|
||||
type,
|
||||
to,
|
||||
caption,
|
||||
album));
|
||||
}
|
||||
if (album) {
|
||||
_sendingAlbums.emplace(album->groupId, album);
|
||||
album->items.reserve(tasks.size());
|
||||
for (const auto &task : tasks) {
|
||||
album->items.push_back(SendingAlbum::Item(task->id()));
|
||||
}
|
||||
}
|
||||
_fileLoader->addTasks(std::move(tasks));
|
||||
}
|
||||
|
||||
void ApiWrap::sendFile(
|
||||
const QByteArray &fileContent,
|
||||
SendMediaType type,
|
||||
const SendOptions &options) {
|
||||
auto to = FileLoadTaskOptions(options);
|
||||
auto caption = QString();
|
||||
_fileLoader->addTask(std::make_unique<FileLoadTask>(
|
||||
QString(),
|
||||
fileContent,
|
||||
nullptr,
|
||||
type,
|
||||
to,
|
||||
caption));
|
||||
}
|
||||
|
||||
void ApiWrap::sendUploadedPhoto(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
bool silent) {
|
||||
if (const auto item = App::histItemById(localId)) {
|
||||
const auto caption = item->getMedia()
|
||||
? item->getMedia()->getCaption()
|
||||
: TextWithEntities();
|
||||
const auto media = MTP_inputMediaUploadedPhoto(
|
||||
MTP_flags(0),
|
||||
file,
|
||||
MTP_string(caption.text),
|
||||
MTPVector<MTPInputDocument>(),
|
||||
MTP_int(0));
|
||||
if (const auto groupId = item->groupId()) {
|
||||
uploadAlbumMedia(item, groupId, media);
|
||||
} else {
|
||||
sendMedia(item, media, silent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::sendUploadedDocument(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
const base::optional<MTPInputFile> &thumb,
|
||||
bool silent) {
|
||||
if (const auto item = App::histItemById(localId)) {
|
||||
auto media = item->getMedia();
|
||||
if (auto document = media ? media->getDocument() : nullptr) {
|
||||
const auto caption = media->getCaption();
|
||||
const auto groupId = item->groupId();
|
||||
const auto flags = MTPDinputMediaUploadedDocument::Flags(0)
|
||||
| (thumb
|
||||
? MTPDinputMediaUploadedDocument::Flag::f_thumb
|
||||
: MTPDinputMediaUploadedDocument::Flag(0))
|
||||
| (groupId
|
||||
? MTPDinputMediaUploadedDocument::Flag::f_nosound_video
|
||||
: MTPDinputMediaUploadedDocument::Flag(0));
|
||||
const auto media = MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
file,
|
||||
thumb ? *thumb : MTPInputFile(),
|
||||
MTP_string(document->mimeString()),
|
||||
ComposeSendingDocumentAttributes(document),
|
||||
MTP_string(caption.text),
|
||||
MTPVector<MTPInputDocument>(),
|
||||
MTP_int(0));
|
||||
if (groupId) {
|
||||
uploadAlbumMedia(item, groupId, media);
|
||||
} else {
|
||||
sendMedia(item, media, silent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::cancelLocalItem(not_null<HistoryItem*> item) {
|
||||
Expects(!IsServerMsgId(item->id));
|
||||
|
||||
if (const auto groupId = item->groupId()) {
|
||||
sendAlbumWithCancelled(item, groupId);
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::uploadAlbumMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId,
|
||||
const MTPInputMedia &media) {
|
||||
const auto localId = item->fullId();
|
||||
const auto failed = [this] {
|
||||
|
||||
};
|
||||
request(MTPmessages_UploadMedia(
|
||||
item->history()->peer->input,
|
||||
media
|
||||
)).done([=](const MTPMessageMedia &result) {
|
||||
const auto item = App::histItemById(localId);
|
||||
if (!item) {
|
||||
failed();
|
||||
}
|
||||
if (const auto media = item->getMedia()) {
|
||||
if (const auto photo = media->getPhoto()) {
|
||||
photo->setWaitingForAlbum();
|
||||
} else if (const auto document = media->getDocument()) {
|
||||
document->setWaitingForAlbum();
|
||||
}
|
||||
}
|
||||
|
||||
switch (result.type()) {
|
||||
case mtpc_messageMediaPhoto: {
|
||||
const auto &data = result.c_messageMediaPhoto();
|
||||
if (data.vphoto.type() != mtpc_photo) {
|
||||
failed();
|
||||
return;
|
||||
}
|
||||
const auto &photo = data.vphoto.c_photo();
|
||||
const auto flags = MTPDinputMediaPhoto::Flags(0)
|
||||
| (data.has_ttl_seconds()
|
||||
? MTPDinputMediaPhoto::Flag::f_ttl_seconds
|
||||
: MTPDinputMediaPhoto::Flag(0));
|
||||
const auto media = MTP_inputMediaPhoto(
|
||||
MTP_flags(flags),
|
||||
MTP_inputPhoto(photo.vid, photo.vaccess_hash),
|
||||
data.has_caption() ? data.vcaption : MTP_string(QString()),
|
||||
data.has_ttl_seconds() ? data.vttl_seconds : MTPint());
|
||||
sendAlbumWithUploaded(item, groupId, media);
|
||||
} break;
|
||||
|
||||
case mtpc_messageMediaDocument: {
|
||||
const auto &data = result.c_messageMediaDocument();
|
||||
if (data.vdocument.type() != mtpc_document) {
|
||||
failed();
|
||||
return;
|
||||
}
|
||||
const auto &document = data.vdocument.c_document();
|
||||
const auto flags = MTPDinputMediaDocument::Flags(0)
|
||||
| (data.has_ttl_seconds()
|
||||
? MTPDinputMediaDocument::Flag::f_ttl_seconds
|
||||
: MTPDinputMediaDocument::Flag(0));
|
||||
const auto media = MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
MTP_inputDocument(document.vid, document.vaccess_hash),
|
||||
data.has_caption() ? data.vcaption : MTP_string(QString()),
|
||||
data.has_ttl_seconds() ? data.vttl_seconds : MTPint());
|
||||
sendAlbumWithUploaded(item, groupId, media);
|
||||
} break;
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
failed();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::sendMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputMedia &media,
|
||||
bool silent) {
|
||||
const auto randomId = rand_value<uint64>();
|
||||
App::historyRegRandom(randomId, item->fullId());
|
||||
|
||||
sendMediaWithRandomId(item, media, silent, randomId);
|
||||
}
|
||||
|
||||
void ApiWrap::sendMediaWithRandomId(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputMedia &media,
|
||||
bool silent,
|
||||
uint64 randomId) {
|
||||
const auto history = item->history();
|
||||
const auto replyTo = item->replyToId();
|
||||
const auto flags = MTPmessages_SendMedia::Flags(0)
|
||||
| (replyTo
|
||||
? MTPmessages_SendMedia::Flag::f_reply_to_msg_id
|
||||
: MTPmessages_SendMedia::Flag(0))
|
||||
| (IsSilentPost(item, silent)
|
||||
? MTPmessages_SendMedia::Flag::f_silent
|
||||
: MTPmessages_SendMedia::Flag(0));
|
||||
history->sendRequestId = request(MTPmessages_SendMedia(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
MTP_int(options.replyTo),
|
||||
MTP_inputMediaContact(
|
||||
MTP_string(phone),
|
||||
MTP_string(firstName),
|
||||
MTP_string(lastName)),
|
||||
MTP_flags(flags),
|
||||
history->peer->input,
|
||||
MTP_int(replyTo),
|
||||
media,
|
||||
MTP_long(randomId),
|
||||
MTPnullMarkup
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
}).fail([](const RPCError &error) {
|
||||
if (error.type() == qstr("PEER_FLOOD")) {
|
||||
Ui::show(Box<InformBox>(
|
||||
PeerFloodErrorText(PeerFloodType::Send)));
|
||||
} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
|
||||
const auto link = textcmdLink(
|
||||
Messenger::Instance().createInternalLinkFull(qsl("spambot")),
|
||||
lang(lng_cant_more_info));
|
||||
Ui::show(Box<InformBox>(lng_error_public_groups_denied(
|
||||
lt_more_info,
|
||||
link)));
|
||||
}
|
||||
}).afterRequest(
|
||||
history->sendRequestId
|
||||
)).done([=](const MTPUpdates &result) { applyUpdates(result);
|
||||
}).fail([=](const RPCError &error) { sendMessageFail(error);
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
}
|
||||
|
||||
App::historyRegRandom(randomId, newId);
|
||||
void ApiWrap::sendAlbumWithUploaded(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId,
|
||||
const MTPInputMedia &media) {
|
||||
const auto localId = item->fullId();
|
||||
const auto randomId = rand_value<uint64>();
|
||||
App::historyRegRandom(randomId, localId);
|
||||
|
||||
const auto albumIt = _sendingAlbums.find(groupId.raw());
|
||||
Assert(albumIt != _sendingAlbums.end());
|
||||
const auto &album = albumIt->second;
|
||||
|
||||
const auto proj = [](const SendingAlbum::Item &item) {
|
||||
return item.msgId;
|
||||
};
|
||||
const auto itemIt = ranges::find(album->items, localId, proj);
|
||||
Assert(itemIt != album->items.end());
|
||||
Assert(!itemIt->media);
|
||||
itemIt->media = MTP_inputSingleMedia(media, MTP_long(randomId));
|
||||
|
||||
sendAlbumIfReady(album.get());
|
||||
}
|
||||
|
||||
void ApiWrap::sendAlbumWithCancelled(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId) {
|
||||
const auto localId = item->fullId();
|
||||
const auto albumIt = _sendingAlbums.find(groupId.raw());
|
||||
Assert(albumIt != _sendingAlbums.end());
|
||||
const auto &album = albumIt->second;
|
||||
|
||||
const auto proj = [](const SendingAlbum::Item &item) {
|
||||
return item.msgId;
|
||||
};
|
||||
const auto itemIt = ranges::find(album->items, localId, proj);
|
||||
Assert(itemIt != album->items.end());
|
||||
album->items.erase(itemIt);
|
||||
|
||||
sendAlbumIfReady(album.get());
|
||||
}
|
||||
|
||||
void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
const auto groupId = album->groupId;
|
||||
if (album->items.empty()) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
return;
|
||||
}
|
||||
auto sample = (HistoryItem*)nullptr;
|
||||
auto medias = QVector<MTPInputSingleMedia>();
|
||||
medias.reserve(album->items.size());
|
||||
for (const auto &item : album->items) {
|
||||
if (!item.media) {
|
||||
return;
|
||||
} else if (!sample) {
|
||||
sample = App::histItemById(item.msgId);
|
||||
}
|
||||
medias.push_back(*item.media);
|
||||
}
|
||||
if (!sample) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
return;
|
||||
} else if (medias.size() < 2) {
|
||||
const auto &single = medias.front().c_inputSingleMedia();
|
||||
sendMediaWithRandomId(
|
||||
sample,
|
||||
single.vmedia,
|
||||
album->silent,
|
||||
single.vrandom_id.v);
|
||||
_sendingAlbums.remove(groupId);
|
||||
return;
|
||||
}
|
||||
const auto history = sample->history();
|
||||
const auto replyTo = sample->replyToId();
|
||||
const auto flags = MTPmessages_SendMultiMedia::Flags(0)
|
||||
| (replyTo
|
||||
? MTPmessages_SendMultiMedia::Flag::f_reply_to_msg_id
|
||||
: MTPmessages_SendMultiMedia::Flag(0))
|
||||
| (IsSilentPost(sample, album->silent)
|
||||
? MTPmessages_SendMultiMedia::Flag::f_silent
|
||||
: MTPmessages_SendMultiMedia::Flag(0));
|
||||
history->sendRequestId = request(MTPmessages_SendMultiMedia(
|
||||
MTP_flags(flags),
|
||||
history->peer->input,
|
||||
MTP_int(replyTo),
|
||||
MTP_vector<MTPInputSingleMedia>(medias)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
applyUpdates(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
sendMessageFail(error);
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
}
|
||||
|
||||
void ApiWrap::readServerHistory(not_null<History*> history) {
|
||||
|
||||
@@ -28,14 +28,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "base/flat_set.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
|
||||
class TaskQueue;
|
||||
class AuthSession;
|
||||
enum class SparseIdsLoadDirection;
|
||||
struct MessageGroupId;
|
||||
struct SendingAlbum;
|
||||
enum class SendMediaType;
|
||||
|
||||
namespace Storage {
|
||||
enum class SharedMediaType : char;
|
||||
struct PreparedList;
|
||||
} // namespace Storage
|
||||
|
||||
enum class SparseIdsLoadDirection;
|
||||
|
||||
namespace Api {
|
||||
|
||||
inline const MTPVector<MTPChat> *getChatsFromMessagesChats(const MTPmessages_Chats &chats) {
|
||||
@@ -52,7 +56,6 @@ class ApiWrap : private MTP::Sender, private base::Subscriber {
|
||||
public:
|
||||
ApiWrap(not_null<AuthSession*> session);
|
||||
|
||||
void start();
|
||||
void applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId = 0);
|
||||
|
||||
using RequestMessageDataCallback = base::lambda<void(ChannelData*, MsgId)>;
|
||||
@@ -66,6 +69,10 @@ public:
|
||||
void requestAdmins(not_null<ChannelData*> channel);
|
||||
void requestParticipantsCountDelayed(not_null<ChannelData*> channel);
|
||||
|
||||
void requestChangelog(
|
||||
const QString &sinceVersion,
|
||||
base::lambda<void(const MTPUpdates &result)> callback);
|
||||
|
||||
void requestChannelMembersForAdd(
|
||||
not_null<ChannelData*> channel,
|
||||
base::lambda<void(const MTPchannels_ChannelParticipants&)> callback);
|
||||
@@ -73,8 +80,14 @@ public:
|
||||
void processFullPeer(UserData *user, const MTPUserFull &result);
|
||||
|
||||
void requestSelfParticipant(ChannelData *channel);
|
||||
void kickParticipant(PeerData *peer, UserData *user, const MTPChannelBannedRights ¤tRights);
|
||||
void unblockParticipant(PeerData *peer, UserData *user);
|
||||
void kickParticipant(not_null<ChatData*> chat, not_null<UserData*> user);
|
||||
void kickParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user,
|
||||
const MTPChannelBannedRights ¤tRights);
|
||||
void unblockParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user);
|
||||
|
||||
void requestWebPageDelayed(WebPageData *page);
|
||||
void clearWebPageRequest(WebPageData *page);
|
||||
@@ -186,6 +199,33 @@ public:
|
||||
void readServerHistory(not_null<History*> history);
|
||||
void readServerHistoryForce(not_null<History*> history);
|
||||
|
||||
void sendVoiceMessage(
|
||||
QByteArray result,
|
||||
VoiceWaveform waveform,
|
||||
int duration,
|
||||
const SendOptions &options);
|
||||
void sendFiles(
|
||||
Storage::PreparedList &&list,
|
||||
SendMediaType type,
|
||||
QString caption,
|
||||
std::shared_ptr<SendingAlbum> album,
|
||||
const SendOptions &options);
|
||||
void sendFile(
|
||||
const QByteArray &fileContent,
|
||||
SendMediaType type,
|
||||
const SendOptions &options);
|
||||
|
||||
void sendUploadedPhoto(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
bool silent);
|
||||
void sendUploadedDocument(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
const base::optional<MTPInputFile> &thumb,
|
||||
bool silent);
|
||||
void cancelLocalItem(not_null<HistoryItem*> item);
|
||||
|
||||
~ApiWrap();
|
||||
|
||||
private:
|
||||
@@ -197,8 +237,6 @@ private:
|
||||
using MessageDataRequests = QMap<MsgId, MessageDataRequest>;
|
||||
using SharedMediaType = Storage::SharedMediaType;
|
||||
|
||||
void requestAppChangelogs();
|
||||
void addLocalChangelogs(int oldAppVersion);
|
||||
void updatesReceived(const MTPUpdates &updates);
|
||||
void checkQuitPreventFinished();
|
||||
|
||||
@@ -283,9 +321,30 @@ private:
|
||||
void applyAffectedMessages(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_AffectedMessages &result);
|
||||
void sendMessageFail(const RPCError &error);
|
||||
void uploadAlbumMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId,
|
||||
const MTPInputMedia &media);
|
||||
void sendAlbumWithUploaded(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId,
|
||||
const MTPInputMedia &media);
|
||||
void sendAlbumWithCancelled(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId);
|
||||
void sendAlbumIfReady(not_null<SendingAlbum*> album);
|
||||
void sendMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputMedia &media,
|
||||
bool silent);
|
||||
void sendMediaWithRandomId(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputMedia &media,
|
||||
bool silent,
|
||||
uint64 randomId);
|
||||
|
||||
not_null<AuthSession*> _session;
|
||||
mtpRequestId _changelogSubscription = 0;
|
||||
|
||||
MessageDataRequests _messageDataRequests;
|
||||
QMap<ChannelData*, MessageDataRequests> _channelMessageDataRequests;
|
||||
@@ -304,9 +363,10 @@ private:
|
||||
mtpRequestId _channelMembersForAddRequestId = 0;
|
||||
base::lambda<void(const MTPchannels_ChannelParticipants&)> _channelMembersForAddCallback;
|
||||
|
||||
typedef QPair<PeerData*, UserData*> KickRequest;
|
||||
typedef QMap<KickRequest, mtpRequestId> KickRequests;
|
||||
KickRequests _kickRequests;
|
||||
using KickRequest = std::pair<
|
||||
not_null<ChannelData*>,
|
||||
not_null<UserData*>>;
|
||||
base::flat_map<KickRequest, mtpRequestId> _kickRequests;
|
||||
|
||||
QMap<ChannelData*, mtpRequestId> _selfParticipantRequests;
|
||||
|
||||
@@ -375,6 +435,8 @@ private:
|
||||
};
|
||||
base::flat_map<not_null<PeerData*>, ReadRequest> _readRequests;
|
||||
base::flat_map<not_null<PeerData*>, MsgId> _readRequestsPending;
|
||||
std::unique_ptr<TaskQueue> _fileLoader;
|
||||
base::flat_map<uint64, std::shared_ptr<SendingAlbum>> _sendingAlbums;
|
||||
|
||||
base::Observable<PeerData*> _fullPeerUpdated;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "history/history_service_layout.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "messenger.h"
|
||||
@@ -46,6 +47,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "numbers.h"
|
||||
#include "observer_peer.h"
|
||||
#include "auth_session.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
@@ -127,7 +129,8 @@ namespace {
|
||||
LastPhotosList lastPhotos;
|
||||
using LastPhotosMap = QHash<PhotoData*, LastPhotosList::iterator>;
|
||||
LastPhotosMap lastPhotosMap;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace App {
|
||||
|
||||
@@ -218,117 +221,6 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
TimeId onlineForSort(UserData *user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return -1;
|
||||
}
|
||||
TimeId online = user->onlineTill;
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
case -1: return online;
|
||||
|
||||
case -2: {
|
||||
QDate yesterday(date(now).date());
|
||||
return int32(QDateTime(yesterday.addDays(-3)).toTime_t()) + (unixtime() - myunixtime());
|
||||
} break;
|
||||
|
||||
case -3: {
|
||||
QDate weekago(date(now).date());
|
||||
return int32(QDateTime(weekago.addDays(-7)).toTime_t()) + (unixtime() - myunixtime());
|
||||
} break;
|
||||
|
||||
case -4: {
|
||||
QDate monthago(date(now).date());
|
||||
return int32(QDateTime(monthago.addDays(-30)).toTime_t()) + (unixtime() - myunixtime());
|
||||
} break;
|
||||
}
|
||||
return -online;
|
||||
}
|
||||
return online;
|
||||
}
|
||||
|
||||
int32 onlineWillChangeIn(UserData *user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return 86400;
|
||||
}
|
||||
return onlineWillChangeIn(user->onlineTill, now);
|
||||
}
|
||||
|
||||
int32 onlineWillChangeIn(TimeId online, TimeId now) {
|
||||
if (online <= 0) {
|
||||
if (-online > now) return std::max(-online - now, 86400);
|
||||
return 86400;
|
||||
}
|
||||
if (online > now) {
|
||||
return std::max(online - now, 86400);
|
||||
}
|
||||
int32 minutes = (now - online) / 60;
|
||||
if (minutes < 60) {
|
||||
return (minutes + 1) * 60 - (now - online);
|
||||
}
|
||||
int32 hours = (now - online) / 3600;
|
||||
if (hours < 12) {
|
||||
return (hours + 1) * 3600 - (now - online);
|
||||
}
|
||||
QDateTime dNow(date(now)), dTomorrow(dNow.date().addDays(1));
|
||||
return std::max(dNow.secsTo(dTomorrow), 86400LL);
|
||||
}
|
||||
|
||||
QString onlineText(UserData *user, TimeId now, bool precise) {
|
||||
if (isNotificationsUser(user->id)) {
|
||||
return lang(lng_status_service_notifications);
|
||||
} else if (user->botInfo) {
|
||||
return lang(lng_status_bot);
|
||||
} else if (isServiceUser(user->id)) {
|
||||
return lang(lng_status_support);
|
||||
}
|
||||
return onlineText(user->onlineTill, now, precise);
|
||||
}
|
||||
|
||||
QString onlineText(TimeId online, TimeId now, bool precise) {
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
case -1: return lang(lng_status_offline);
|
||||
case -2: return lang(lng_status_recently);
|
||||
case -3: return lang(lng_status_last_week);
|
||||
case -4: return lang(lng_status_last_month);
|
||||
}
|
||||
return (-online > now) ? lang(lng_status_online) : lang(lng_status_recently);
|
||||
}
|
||||
if (online > now) {
|
||||
return lang(lng_status_online);
|
||||
}
|
||||
QString when;
|
||||
if (precise) {
|
||||
QDateTime dOnline(date(online)), dNow(date(now));
|
||||
if (dOnline.date() == dNow.date()) {
|
||||
return lng_status_lastseen_today(lt_time, dOnline.time().toString(cTimeFormat()));
|
||||
} else if (dOnline.date().addDays(1) == dNow.date()) {
|
||||
return lng_status_lastseen_yesterday(lt_time, dOnline.time().toString(cTimeFormat()));
|
||||
}
|
||||
return lng_status_lastseen_date_time(lt_date, dOnline.date().toString(qsl("dd.MM.yy")), lt_time, dOnline.time().toString(cTimeFormat()));
|
||||
}
|
||||
int32 minutes = (now - online) / 60;
|
||||
if (!minutes) {
|
||||
return lang(lng_status_lastseen_now);
|
||||
} else if (minutes < 60) {
|
||||
return lng_status_lastseen_minutes(lt_count, minutes);
|
||||
}
|
||||
int32 hours = (now - online) / 3600;
|
||||
if (hours < 12) {
|
||||
return lng_status_lastseen_hours(lt_count, hours);
|
||||
}
|
||||
QDateTime dOnline(date(online)), dNow(date(now));
|
||||
if (dOnline.date() == dNow.date()) {
|
||||
return lng_status_lastseen_today(lt_time, dOnline.time().toString(cTimeFormat()));
|
||||
} else if (dOnline.date().addDays(1) == dNow.date()) {
|
||||
return lng_status_lastseen_yesterday(lt_time, dOnline.time().toString(cTimeFormat()));
|
||||
}
|
||||
return lng_status_lastseen_date(lt_date, dOnline.date().toString(qsl("dd.MM.yy")));
|
||||
}
|
||||
|
||||
namespace {
|
||||
// we should get a full restriction in "{fulltype}: {reason}" format and we
|
||||
// need to find a "-all" tag in {fulltype}, otherwise ignore this restriction
|
||||
@@ -353,27 +245,6 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
bool onlineColorUse(UserData *user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return false;
|
||||
}
|
||||
return onlineColorUse(user->onlineTill, now);
|
||||
}
|
||||
|
||||
bool onlineColorUse(TimeId online, TimeId now) {
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
case -1:
|
||||
case -2:
|
||||
case -3:
|
||||
case -4: return false;
|
||||
}
|
||||
return (-online > now);
|
||||
}
|
||||
return (online > now);
|
||||
}
|
||||
|
||||
UserData *feedUser(const MTPUser &user) {
|
||||
UserData *data = nullptr;
|
||||
bool wasContact = false, minimal = false;
|
||||
@@ -472,7 +343,7 @@ namespace {
|
||||
QString pname = (showPhoneChanged || phoneChanged || nameChanged) ? ((showPhone && !phone.isEmpty()) ? formatPhone(phone) : QString()) : data->nameOrPhone;
|
||||
|
||||
if (!minimal && d.is_self() && uname != data->username) {
|
||||
SignalHandlers::setCrashAnnotation("Username", uname);
|
||||
CrashReports::SetAnnotation("Username", uname);
|
||||
}
|
||||
data->setName(fname, lname, pname, uname);
|
||||
if (d.has_photo()) {
|
||||
@@ -648,26 +519,34 @@ namespace {
|
||||
}
|
||||
} break;
|
||||
case mtpc_channel: {
|
||||
auto &d = chat.c_channel();
|
||||
const auto &d = chat.c_channel();
|
||||
|
||||
auto peerId = peerFromChannel(d.vid.v);
|
||||
const auto peerId = peerFromChannel(d.vid.v);
|
||||
minimal = d.is_min();
|
||||
if (minimal) {
|
||||
data = App::channelLoaded(peerId);
|
||||
if (!data) {
|
||||
return nullptr; // minimal is not loaded, need to make getDifference
|
||||
// Can't apply minimal to a not loaded channel.
|
||||
// Need to make getDifference.
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
data = App::channel(peerId);
|
||||
data->input = MTP_inputPeerChannel(d.vid, d.has_access_hash() ? d.vaccess_hash : MTP_long(0));
|
||||
const auto accessHash = d.has_access_hash()
|
||||
? d.vaccess_hash
|
||||
: MTP_long(0);
|
||||
data->input = MTP_inputPeerChannel(d.vid, accessHash);
|
||||
}
|
||||
|
||||
auto cdata = data->asChannel();
|
||||
auto wasInChannel = cdata->amIn();
|
||||
auto canViewAdmins = cdata->canViewAdmins();
|
||||
auto canViewMembers = cdata->canViewMembers();
|
||||
auto canAddMembers = cdata->canAddMembers();
|
||||
const auto cdata = data->asChannel();
|
||||
const auto wasInChannel = cdata->amIn();
|
||||
const auto canViewAdmins = cdata->canViewAdmins();
|
||||
const auto canViewMembers = cdata->canViewMembers();
|
||||
const auto canAddMembers = cdata->canAddMembers();
|
||||
|
||||
if (d.has_participants_count()) {
|
||||
cdata->setMembersCount(d.vparticipants_count.v);
|
||||
}
|
||||
if (minimal) {
|
||||
auto mask = 0
|
||||
| MTPDchannel::Flag::f_broadcast
|
||||
@@ -1033,26 +912,22 @@ namespace {
|
||||
if (m.has_from_id() && peerId == Auth().userPeerId()) {
|
||||
peerId = peerFromUser(m.vfrom_id);
|
||||
}
|
||||
if (auto existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
|
||||
if (const auto existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
|
||||
auto text = qs(m.vmessage);
|
||||
auto entities = m.has_entities() ? TextUtilities::EntitiesFromMTP(m.ventities.v) : EntitiesInText();
|
||||
auto entities = m.has_entities()
|
||||
? TextUtilities::EntitiesFromMTP(m.ventities.v)
|
||||
: EntitiesInText();
|
||||
existing->setText({ text, entities });
|
||||
existing->updateMedia(m.has_media() ? (&m.vmedia) : nullptr);
|
||||
existing->updateReplyMarkup(m.has_reply_markup() ? (&m.vreply_markup) : nullptr);
|
||||
existing->updateReplyMarkup(m.has_reply_markup()
|
||||
? (&m.vreply_markup)
|
||||
: nullptr);
|
||||
existing->setViewsCount(m.has_views() ? m.vviews.v : -1);
|
||||
existing->addToUnreadMentions(AddToUnreadMentionsMethod::New);
|
||||
if (auto sharedMediaTypes = existing->sharedMediaTypes()) {
|
||||
Auth().storage().add(Storage::SharedMediaAddNew(
|
||||
peerId,
|
||||
sharedMediaTypes,
|
||||
existing->id));
|
||||
}
|
||||
|
||||
existing->indexAsNewItem();
|
||||
if (!existing->detached()) {
|
||||
App::checkSavedGif(existing);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
@@ -1104,29 +979,23 @@ namespace {
|
||||
}
|
||||
|
||||
void feedMsgs(const QVector<MTPMessage> &msgs, NewMessageType type) {
|
||||
QMap<uint64, int32> msgsIds;
|
||||
for (int32 i = 0, l = msgs.size(); i < l; ++i) {
|
||||
const auto &msg(msgs.at(i));
|
||||
switch (msg.type()) {
|
||||
case mtpc_message: {
|
||||
const auto &d(msg.c_message());
|
||||
bool needToAdd = true;
|
||||
auto indices = base::flat_map<uint64, int>();
|
||||
for (int i = 0, l = msgs.size(); i != l; ++i) {
|
||||
const auto &msg = msgs[i];
|
||||
if (msg.type() == mtpc_message) {
|
||||
const auto &data = msg.c_message();
|
||||
if (type == NewMessageUnread) { // new message, index my forwarded messages to links overview
|
||||
if (checkEntitiesAndViewsUpdate(d)) { // already in blocks
|
||||
if (checkEntitiesAndViewsUpdate(data)) { // already in blocks
|
||||
LOG(("Skipping message, because it is already in blocks!"));
|
||||
needToAdd = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (needToAdd) {
|
||||
msgsIds.insert((uint64(uint32(d.vid.v)) << 32) | uint64(i), i);
|
||||
}
|
||||
} break;
|
||||
case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i); break;
|
||||
case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i); break;
|
||||
}
|
||||
const auto msgId = idFromMessage(msg);
|
||||
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
|
||||
}
|
||||
for (QMap<uint64, int32>::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) {
|
||||
histories().addNewMessage(msgs.at(i.value()), type);
|
||||
for (const auto [position, index] : indices) {
|
||||
histories().addNewMessage(msgs[index], type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1421,7 +1290,23 @@ namespace {
|
||||
}
|
||||
|
||||
WebPageData *feedWebPage(const MTPDwebPagePending &webpage, WebPageData *convert) {
|
||||
return App::webPageSet(webpage.vid.v, convert, QString(), QString(), QString(), QString(), QString(), TextWithEntities(), nullptr, nullptr, 0, QString(), webpage.vdate.v);
|
||||
constexpr auto kDefaultPendingTimeout = 60;
|
||||
return App::webPageSet(
|
||||
webpage.vid.v,
|
||||
convert,
|
||||
QString(),
|
||||
QString(),
|
||||
QString(),
|
||||
QString(),
|
||||
QString(),
|
||||
TextWithEntities(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
0,
|
||||
QString(),
|
||||
webpage.vdate.v
|
||||
? webpage.vdate.v
|
||||
: (unixtime() + kDefaultPendingTimeout));
|
||||
}
|
||||
|
||||
WebPageData *feedWebPage(const MTPWebPage &webpage) {
|
||||
@@ -1514,22 +1399,29 @@ namespace {
|
||||
}
|
||||
|
||||
PhotoData *photo(const PhotoId &photo) {
|
||||
PhotosData::const_iterator i = ::photosData.constFind(photo);
|
||||
auto i = ::photosData.constFind(photo);
|
||||
if (i == ::photosData.cend()) {
|
||||
i = ::photosData.insert(photo, new PhotoData(photo));
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
PhotoData *photoSet(const PhotoId &photo, PhotoData *convert, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full) {
|
||||
PhotoData *photoSet(
|
||||
const PhotoId &photo,
|
||||
PhotoData *convert,
|
||||
const uint64 &access,
|
||||
int32 date,
|
||||
const ImagePtr &thumb,
|
||||
const ImagePtr &medium,
|
||||
const ImagePtr &full) {
|
||||
if (convert) {
|
||||
if (convert->id != photo) {
|
||||
PhotosData::iterator i = ::photosData.find(convert->id);
|
||||
const auto i = ::photosData.find(convert->id);
|
||||
if (i != ::photosData.cend() && i.value() == convert) {
|
||||
::photosData.erase(i);
|
||||
}
|
||||
convert->id = photo;
|
||||
convert->uploadingData.reset();
|
||||
convert->uploadingData = nullptr;
|
||||
}
|
||||
if (date) {
|
||||
convert->access = access;
|
||||
@@ -1539,9 +1431,9 @@ namespace {
|
||||
updateImage(convert->full, full);
|
||||
}
|
||||
}
|
||||
PhotosData::const_iterator i = ::photosData.constFind(photo);
|
||||
const auto i = ::photosData.constFind(photo);
|
||||
PhotoData *result;
|
||||
LastPhotosMap::iterator inLastIter = lastPhotosMap.end();
|
||||
auto inLastIter = lastPhotosMap.end();
|
||||
if (i == ::photosData.cend()) {
|
||||
if (convert) {
|
||||
result = convert;
|
||||
@@ -1575,27 +1467,39 @@ namespace {
|
||||
}
|
||||
|
||||
DocumentData *document(const DocumentId &document) {
|
||||
DocumentsData::const_iterator i = ::documentsData.constFind(document);
|
||||
auto i = ::documentsData.constFind(document);
|
||||
if (i == ::documentsData.cend()) {
|
||||
i = ::documentsData.insert(document, DocumentData::create(document));
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 version, int32 date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation) {
|
||||
DocumentData *documentSet(
|
||||
const DocumentId &document,
|
||||
DocumentData *convert,
|
||||
const uint64 &access,
|
||||
int32 version,
|
||||
int32 date,
|
||||
const QVector<MTPDocumentAttribute> &attributes,
|
||||
const QString &mime,
|
||||
const ImagePtr &thumb,
|
||||
int32 dc,
|
||||
int32 size,
|
||||
const StorageImageLocation &thumbLocation) {
|
||||
bool versionChanged = false;
|
||||
bool sentSticker = false;
|
||||
if (convert) {
|
||||
MediaKey oldKey = convert->mediaKey();
|
||||
bool idChanged = (convert->id != document);
|
||||
const auto oldKey = convert->mediaKey();
|
||||
const auto idChanged = (convert->id != document);
|
||||
if (idChanged) {
|
||||
DocumentsData::iterator i = ::documentsData.find(convert->id);
|
||||
const auto i = ::documentsData.find(convert->id);
|
||||
if (i != ::documentsData.cend() && i.value() == convert) {
|
||||
::documentsData.erase(i);
|
||||
}
|
||||
|
||||
convert->id = document;
|
||||
convert->status = FileReady;
|
||||
convert->uploadingData = nullptr;
|
||||
sentSticker = (convert->sticker() != 0);
|
||||
}
|
||||
if (date) {
|
||||
@@ -1613,7 +1517,7 @@ namespace {
|
||||
convert->sticker()->loc = thumbLocation;
|
||||
}
|
||||
|
||||
MediaKey newKey = convert->mediaKey();
|
||||
const auto newKey = convert->mediaKey();
|
||||
if (idChanged) {
|
||||
if (convert->isVoiceMessage()) {
|
||||
Local::copyAudio(oldKey, newKey);
|
||||
@@ -1627,7 +1531,7 @@ namespace {
|
||||
Local::writeSavedGifs();
|
||||
}
|
||||
}
|
||||
DocumentsData::const_iterator i = ::documentsData.constFind(document);
|
||||
const auto i = ::documentsData.constFind(document);
|
||||
DocumentData *result;
|
||||
if (i == ::documentsData.cend()) {
|
||||
if (convert) {
|
||||
@@ -1704,40 +1608,60 @@ namespace {
|
||||
return i.value();
|
||||
}
|
||||
|
||||
WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const TextWithEntities &description, PhotoData *photo, DocumentData *document, int32 duration, const QString &author, int32 pendingTill) {
|
||||
WebPageData *webPageSet(
|
||||
const WebPageId &webPage,
|
||||
WebPageData *convert,
|
||||
const QString &type,
|
||||
const QString &url,
|
||||
const QString &displayUrl,
|
||||
const QString &siteName,
|
||||
const QString &title,
|
||||
const TextWithEntities &description,
|
||||
PhotoData *photo,
|
||||
DocumentData *document,
|
||||
int duration,
|
||||
const QString &author,
|
||||
int pendingTill) {
|
||||
if (convert) {
|
||||
if (convert->id != webPage) {
|
||||
auto i = webPagesData.find(convert->id);
|
||||
const auto i = webPagesData.find(convert->id);
|
||||
if (i != webPagesData.cend() && i.value() == convert) {
|
||||
webPagesData.erase(i);
|
||||
}
|
||||
convert->id = webPage;
|
||||
}
|
||||
if ((convert->url.isEmpty() && !url.isEmpty()) || (convert->pendingTill && convert->pendingTill != pendingTill && pendingTill >= -1)) {
|
||||
convert->type = toWebPageType(type);
|
||||
convert->url = TextUtilities::Clean(url);
|
||||
convert->displayUrl = TextUtilities::Clean(displayUrl);
|
||||
convert->siteName = TextUtilities::Clean(siteName);
|
||||
convert->title = TextUtilities::SingleLine(title);
|
||||
convert->description = description;
|
||||
convert->photo = photo;
|
||||
convert->document = document;
|
||||
convert->duration = duration;
|
||||
convert->author = TextUtilities::Clean(author);
|
||||
if (convert->pendingTill > 0 && pendingTill <= 0) {
|
||||
Auth().api().clearWebPageRequest(convert);
|
||||
}
|
||||
convert->pendingTill = pendingTill;
|
||||
if (App::main()) App::main()->webPageUpdated(convert);
|
||||
}
|
||||
convert->applyChanges(
|
||||
type,
|
||||
url,
|
||||
displayUrl,
|
||||
siteName,
|
||||
title,
|
||||
description,
|
||||
photo,
|
||||
document,
|
||||
duration,
|
||||
author,
|
||||
pendingTill);
|
||||
}
|
||||
auto i = webPagesData.constFind(webPage);
|
||||
const auto i = webPagesData.constFind(webPage);
|
||||
WebPageData *result;
|
||||
if (i == webPagesData.cend()) {
|
||||
if (convert) {
|
||||
result = convert;
|
||||
} else {
|
||||
result = new WebPageData(webPage, toWebPageType(type), url, displayUrl, siteName, title, description, document, photo, duration, author, (pendingTill >= -1) ? pendingTill : -1);
|
||||
result = new WebPageData(
|
||||
webPage,
|
||||
toWebPageType(type),
|
||||
url,
|
||||
displayUrl,
|
||||
siteName,
|
||||
title,
|
||||
description,
|
||||
document,
|
||||
photo,
|
||||
duration,
|
||||
author,
|
||||
(pendingTill >= -1) ? pendingTill : -1);
|
||||
if (pendingTill > 0) {
|
||||
Auth().api().requestWebPageDelayed(result);
|
||||
}
|
||||
@@ -1746,23 +1670,18 @@ namespace {
|
||||
} else {
|
||||
result = i.value();
|
||||
if (result != convert) {
|
||||
if ((result->url.isEmpty() && !url.isEmpty()) || (result->pendingTill && result->pendingTill != pendingTill && pendingTill >= -1)) {
|
||||
result->type = toWebPageType(type);
|
||||
result->url = TextUtilities::Clean(url);
|
||||
result->displayUrl = TextUtilities::Clean(displayUrl);
|
||||
result->siteName = TextUtilities::Clean(siteName);
|
||||
result->title = TextUtilities::SingleLine(title);
|
||||
result->description = description;
|
||||
result->photo = photo;
|
||||
result->document = document;
|
||||
result->duration = duration;
|
||||
result->author = TextUtilities::Clean(author);
|
||||
if (result->pendingTill > 0 && pendingTill <= 0) {
|
||||
Auth().api().clearWebPageRequest(result);
|
||||
}
|
||||
result->pendingTill = pendingTill;
|
||||
if (App::main()) App::main()->webPageUpdated(result);
|
||||
}
|
||||
result->applyChanges(
|
||||
type,
|
||||
url,
|
||||
displayUrl,
|
||||
siteName,
|
||||
title,
|
||||
description,
|
||||
photo,
|
||||
document,
|
||||
duration,
|
||||
author,
|
||||
pendingTill);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -2122,15 +2041,6 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
int msgRadius() {
|
||||
static int MsgRadius = ([]() {
|
||||
return st::historyMessageRadius;
|
||||
auto minMsgHeight = (st::msgPadding.top() + st::msgFont->height + st::msgPadding.bottom());
|
||||
return minMsgHeight / 2;
|
||||
})();
|
||||
return MsgRadius;
|
||||
}
|
||||
|
||||
void createMaskCorners() {
|
||||
QImage mask[4];
|
||||
prepareCorners(SmallMaskCorners, st::buttonRadius, QColor(255, 255, 255), nullptr, mask);
|
||||
@@ -2138,7 +2048,7 @@ namespace {
|
||||
::cornersMaskSmall[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
::cornersMaskSmall[i].setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
prepareCorners(LargeMaskCorners, msgRadius(), QColor(255, 255, 255), nullptr, mask);
|
||||
prepareCorners(LargeMaskCorners, st::historyMessageRadius, QColor(255, 255, 255), nullptr, mask);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
::cornersMaskLarge[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
::cornersMaskLarge[i].setDevicePixelRatio(cRetinaFactor());
|
||||
@@ -2152,12 +2062,12 @@ namespace {
|
||||
prepareCorners(StickerCorners, st::dateRadius, st::msgServiceBg);
|
||||
prepareCorners(StickerSelectedCorners, st::dateRadius, st::msgServiceBgSelected);
|
||||
prepareCorners(SelectedOverlaySmallCorners, st::buttonRadius, st::msgSelectOverlay);
|
||||
prepareCorners(SelectedOverlayLargeCorners, msgRadius(), st::msgSelectOverlay);
|
||||
prepareCorners(SelectedOverlayLargeCorners, st::historyMessageRadius, st::msgSelectOverlay);
|
||||
prepareCorners(DateCorners, st::dateRadius, st::msgDateImgBg);
|
||||
prepareCorners(DateSelectedCorners, st::dateRadius, st::msgDateImgBgSelected);
|
||||
prepareCorners(InShadowCorners, msgRadius(), st::msgInShadow);
|
||||
prepareCorners(InSelectedShadowCorners, msgRadius(), st::msgInShadowSelected);
|
||||
prepareCorners(ForwardCorners, msgRadius(), st::historyForwardChooseBg);
|
||||
prepareCorners(InShadowCorners, st::historyMessageRadius, st::msgInShadow);
|
||||
prepareCorners(InSelectedShadowCorners, st::historyMessageRadius, st::msgInShadowSelected);
|
||||
prepareCorners(ForwardCorners, st::historyMessageRadius, st::historyForwardChooseBg);
|
||||
prepareCorners(MediaviewSaveCorners, st::mediaviewControllerRadius, st::mediaviewSaveMsgBg);
|
||||
prepareCorners(EmojiHoverCorners, st::buttonRadius, st::emojiPanHover);
|
||||
prepareCorners(StickerHoverCorners, st::buttonRadius, st::emojiPanHover);
|
||||
@@ -2169,10 +2079,10 @@ namespace {
|
||||
prepareCorners(Doc3Corners, st::buttonRadius, st::msgFile3Bg);
|
||||
prepareCorners(Doc4Corners, st::buttonRadius, st::msgFile4Bg);
|
||||
|
||||
prepareCorners(MessageInCorners, msgRadius(), st::msgInBg, &st::msgInShadow);
|
||||
prepareCorners(MessageInSelectedCorners, msgRadius(), st::msgInBgSelected, &st::msgInShadowSelected);
|
||||
prepareCorners(MessageOutCorners, msgRadius(), st::msgOutBg, &st::msgOutShadow);
|
||||
prepareCorners(MessageOutSelectedCorners, msgRadius(), st::msgOutBgSelected, &st::msgOutShadowSelected);
|
||||
prepareCorners(MessageInCorners, st::historyMessageRadius, st::msgInBg, &st::msgInShadow);
|
||||
prepareCorners(MessageInSelectedCorners, st::historyMessageRadius, st::msgInBgSelected, &st::msgInShadowSelected);
|
||||
prepareCorners(MessageOutCorners, st::historyMessageRadius, st::msgOutBg, &st::msgOutShadow);
|
||||
prepareCorners(MessageOutSelectedCorners, st::historyMessageRadius, st::msgOutBgSelected, &st::msgOutShadowSelected);
|
||||
}
|
||||
|
||||
void createCorners() {
|
||||
@@ -2647,42 +2557,46 @@ namespace {
|
||||
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
}
|
||||
|
||||
void complexAdjustRect(ImageRoundCorners corners, QRect &rect, RectParts &parts) {
|
||||
if (corners & ImageRoundCorner::TopLeft) {
|
||||
if (!(corners & ImageRoundCorner::BottomLeft)) {
|
||||
parts = RectPart::NoTopBottom | RectPart::FullTop;
|
||||
rect.setHeight(rect.height() + msgRadius());
|
||||
void rectWithCorners(Painter &p, QRect rect, const style::color &bg, RoundCorners index, RectParts corners) {
|
||||
auto parts = RectPart::Top
|
||||
| RectPart::NoTopBottom
|
||||
| RectPart::Bottom
|
||||
| corners;
|
||||
roundRect(p, rect, bg, index, nullptr, parts);
|
||||
if ((corners & RectPart::AllCorners) != RectPart::AllCorners) {
|
||||
const auto size = ::corners[index].p[0].width() / cIntRetinaFactor();
|
||||
if (!(corners & RectPart::TopLeft)) {
|
||||
p.fillRect(rect.x(), rect.y(), size, size, bg);
|
||||
}
|
||||
if (!(corners & RectPart::TopRight)) {
|
||||
p.fillRect(rect.x() + rect.width() - size, rect.y(), size, size, bg);
|
||||
}
|
||||
if (!(corners & RectPart::BottomLeft)) {
|
||||
p.fillRect(rect.x(), rect.y() + rect.height() - size, size, size, bg);
|
||||
}
|
||||
if (!(corners & RectPart::BottomRight)) {
|
||||
p.fillRect(rect.x() + rect.width() - size, rect.y() + rect.height() - size, size, size, bg);
|
||||
}
|
||||
} else if (corners & ImageRoundCorner::BottomLeft) {
|
||||
parts = RectPart::NoTopBottom | RectPart::FullBottom;
|
||||
rect.setTop(rect.y() - msgRadius());
|
||||
} else {
|
||||
parts = RectPart::NoTopBottom;
|
||||
rect.setTop(rect.y() - msgRadius());
|
||||
rect.setHeight(rect.height() + msgRadius());
|
||||
}
|
||||
}
|
||||
|
||||
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners) {
|
||||
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners) {
|
||||
if (radius == ImageRoundRadius::Ellipse) {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(p.textPalette().selectOverlay);
|
||||
p.drawEllipse(rect);
|
||||
} else {
|
||||
auto overlayCorners = (radius == ImageRoundRadius::Small) ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners;
|
||||
auto overlayParts = RectPart::Full | RectPart::None;
|
||||
if (radius == ImageRoundRadius::Large) {
|
||||
complexAdjustRect(corners, rect, overlayParts);
|
||||
}
|
||||
roundRect(p, rect, p.textPalette().selectOverlay, overlayCorners, nullptr, overlayParts);
|
||||
auto overlayCorners = (radius == ImageRoundRadius::Small)
|
||||
? SelectedOverlaySmallCorners
|
||||
: SelectedOverlayLargeCorners;
|
||||
const auto bg = p.textPalette().selectOverlay;
|
||||
rectWithCorners(p, rect, bg, overlayCorners, corners);
|
||||
}
|
||||
}
|
||||
|
||||
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners) {
|
||||
auto parts = RectPart::Full | RectPart::None;
|
||||
complexAdjustRect(corners, rect, parts);
|
||||
roundRect(p, rect, st::msgInBg, MessageInCorners, nullptr, parts);
|
||||
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners) {
|
||||
rectWithCorners(p, rect, st::msgInBg, MessageInCorners, corners);
|
||||
}
|
||||
|
||||
QImage *cornersMask(ImageRoundRadius radius) {
|
||||
@@ -2766,7 +2680,7 @@ namespace {
|
||||
QImage images[4];
|
||||
switch (radius) {
|
||||
case ImageRoundRadius::Small: prepareCorners(SmallMaskCorners, st::buttonRadius, bg, nullptr, images); break;
|
||||
case ImageRoundRadius::Large: prepareCorners(LargeMaskCorners, msgRadius(), bg, nullptr, images); break;
|
||||
case ImageRoundRadius::Large: prepareCorners(LargeMaskCorners, st::historyMessageRadius, bg, nullptr, images); break;
|
||||
default: p.fillRect(x, y, w, h, bg); return;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,14 +52,6 @@ namespace App {
|
||||
|
||||
QString formatPhone(QString phone);
|
||||
|
||||
TimeId onlineForSort(UserData *user, TimeId now);
|
||||
int32 onlineWillChangeIn(UserData *user, TimeId now);
|
||||
int32 onlineWillChangeIn(TimeId online, TimeId now);
|
||||
QString onlineText(UserData *user, TimeId now, bool precise = false);
|
||||
QString onlineText(TimeId online, TimeId now, bool precise = false);
|
||||
bool onlineColorUse(UserData *user, TimeId now);
|
||||
bool onlineColorUse(TimeId online, TimeId now);
|
||||
|
||||
UserData *feedUser(const MTPUser &user);
|
||||
UserData *feedUsers(const MTPVector<MTPUser> &users); // returns last user
|
||||
PeerData *feedChat(const MTPChat &chat);
|
||||
@@ -141,13 +133,52 @@ namespace App {
|
||||
PeerData *peerByName(const QString &username);
|
||||
QString peerName(const PeerData *peer, bool forDialogs = false);
|
||||
PhotoData *photo(const PhotoId &photo);
|
||||
PhotoData *photoSet(const PhotoId &photo, PhotoData *convert, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full);
|
||||
PhotoData *photoSet(
|
||||
const PhotoId &photo,
|
||||
PhotoData *convert,
|
||||
const uint64 &access,
|
||||
int32 date,
|
||||
const ImagePtr &thumb,
|
||||
const ImagePtr &medium,
|
||||
const ImagePtr &full);
|
||||
DocumentData *document(const DocumentId &document);
|
||||
DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 version, int32 date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation);
|
||||
DocumentData *documentSet(
|
||||
const DocumentId &document,
|
||||
DocumentData *convert,
|
||||
const uint64 &access,
|
||||
int32 version,
|
||||
int32 date,
|
||||
const QVector<MTPDocumentAttribute> &attributes,
|
||||
const QString &mime,
|
||||
const ImagePtr &thumb,
|
||||
int32 dc,
|
||||
int32 size,
|
||||
const StorageImageLocation &thumbLocation);
|
||||
WebPageData *webPage(const WebPageId &webPage);
|
||||
WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const TextWithEntities &description, PhotoData *photo, DocumentData *doc, int32 duration, const QString &author, int32 pendingTill);
|
||||
WebPageData *webPageSet(
|
||||
const WebPageId &webPage,
|
||||
WebPageData *convert,
|
||||
const QString &type,
|
||||
const QString &url,
|
||||
const QString &displayUrl,
|
||||
const QString &siteName,
|
||||
const QString &title,
|
||||
const TextWithEntities &description,
|
||||
PhotoData *photo,
|
||||
DocumentData *document,
|
||||
int duration,
|
||||
const QString &author,
|
||||
int pendingTill);
|
||||
GameData *game(const GameId &game);
|
||||
GameData *gameSet(const GameId &game, GameData *convert, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *doc);
|
||||
GameData *gameSet(
|
||||
const GameId &game,
|
||||
GameData *convert,
|
||||
const uint64 &accessHash,
|
||||
const QString &shortName,
|
||||
const QString &title,
|
||||
const QString &description,
|
||||
PhotoData *photo,
|
||||
DocumentData *document);
|
||||
LocationData *location(const LocationCoords &coords);
|
||||
void forgetMedia();
|
||||
|
||||
@@ -270,8 +301,8 @@ namespace App {
|
||||
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
void setProxySettings(QTcpSocket &socket);
|
||||
|
||||
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners);
|
||||
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners);
|
||||
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners);
|
||||
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners);
|
||||
|
||||
QImage *cornersMask(ImageRoundRadius radius);
|
||||
void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, RoundCorners index, const style::color *shadow = nullptr, RectParts parts = RectPart::Full);
|
||||
|
||||
@@ -26,8 +26,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "storage/localstorage.h"
|
||||
#include "autoupdater.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "messenger.h"
|
||||
#include "base/timer.h"
|
||||
#include "core/crash_report_window.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -71,8 +73,13 @@ QString _escapeFrom7bit(const QString &str) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Application::Application(int &argc, char **argv) : QApplication(argc, argv) {
|
||||
QByteArray d(QFile::encodeName(QDir(cWorkingDir()).absolutePath()));
|
||||
Application::Application(
|
||||
not_null<Core::Launcher*> launcher,
|
||||
int &argc,
|
||||
char **argv)
|
||||
: QApplication(argc, argv)
|
||||
, _launcher(launcher) {
|
||||
const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath());
|
||||
char h[33] = { 0 };
|
||||
hashMd5Hex(d.constData(), d.size(), h);
|
||||
#ifndef OS_MAC_STORE
|
||||
@@ -206,12 +213,12 @@ void Application::singleInstanceChecked() {
|
||||
if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
|
||||
new NotStartedWindow();
|
||||
} else {
|
||||
SignalHandlers::Status status = SignalHandlers::start();
|
||||
if (status == SignalHandlers::CantOpen) {
|
||||
const auto status = CrashReports::Start();
|
||||
if (status == CrashReports::CantOpen) {
|
||||
new NotStartedWindow();
|
||||
} else if (status == SignalHandlers::LastCrashed) {
|
||||
} else if (status == CrashReports::LastCrashed) {
|
||||
if (Sandbox::LastCrashDump().isEmpty()) { // don't handle bad closing for now
|
||||
if (SignalHandlers::restart() == SignalHandlers::CantOpen) {
|
||||
if (CrashReports::Restart() == CrashReports::CantOpen) {
|
||||
new NotStartedWindow();
|
||||
} else {
|
||||
Sandbox::launch();
|
||||
@@ -313,7 +320,7 @@ void Application::startApplication() {
|
||||
|
||||
void Application::createMessenger() {
|
||||
Expects(!App::quitting());
|
||||
_messengerInstance = std::make_unique<Messenger>();
|
||||
_messengerInstance = std::make_unique<Messenger>(_launcher);
|
||||
}
|
||||
|
||||
void Application::closeApplication() {
|
||||
|
||||
@@ -21,11 +21,16 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#pragma once
|
||||
|
||||
class UpdateChecker;
|
||||
|
||||
namespace Core {
|
||||
class Launcher;
|
||||
} // namespace Core
|
||||
|
||||
class Application : public QApplication {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Application(int &argc, char **argv);
|
||||
Application(not_null<Core::Launcher*> launcher, int &argc, char **argv);
|
||||
|
||||
bool event(QEvent *e) override;
|
||||
|
||||
@@ -55,6 +60,7 @@ private:
|
||||
typedef QPair<QLocalSocket*, QByteArray> LocalClient;
|
||||
typedef QList<LocalClient> LocalClients;
|
||||
|
||||
not_null<Core::Launcher*> _launcher;
|
||||
std::unique_ptr<Messenger> _messengerInstance;
|
||||
|
||||
QString _localServerName, _localSocketReadData;
|
||||
|
||||
@@ -22,16 +22,20 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "messenger.h"
|
||||
#include "core/changelogs.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/serialize_common.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "window/section_widget.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "boxes/send_files_box.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -40,7 +44,8 @@ constexpr auto kAutoLockTimeoutLateMs = TimeMs(3000);
|
||||
} // namespace
|
||||
|
||||
AuthSessionData::Variables::Variables()
|
||||
: selectorTab(ChatHelpers::SelectorTab::Emoji)
|
||||
: sendFilesWay(SendFilesWay::Album)
|
||||
, selectorTab(ChatHelpers::SelectorTab::Emoji)
|
||||
, floatPlayerColumn(Window::Column::Second)
|
||||
, floatPlayerCorner(RectPart::TopRight) {
|
||||
}
|
||||
@@ -79,6 +84,7 @@ QByteArray AuthSessionData::serialize() const {
|
||||
1000000));
|
||||
stream << qint32(_variables.thirdColumnWidth.current());
|
||||
stream << qint32(_variables.thirdSectionExtendedBy);
|
||||
stream << qint32(_variables.sendFilesWay);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -103,6 +109,7 @@ void AuthSessionData::constructFromSerialized(const QByteArray &serialized) {
|
||||
float64 dialogsWidthRatio = _variables.dialogsWidthRatio.current();
|
||||
int thirdColumnWidth = _variables.thirdColumnWidth.current();
|
||||
int thirdSectionExtendedBy = _variables.thirdSectionExtendedBy;
|
||||
qint32 sendFilesWay = static_cast<qint32>(_variables.sendFilesWay);
|
||||
stream >> selectorTab;
|
||||
stream >> lastSeenWarningSeen;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -188,6 +195,12 @@ void AuthSessionData::constructFromSerialized(const QByteArray &serialized) {
|
||||
if (_variables.thirdSectionInfoEnabled) {
|
||||
_variables.tabbedSelectorSectionEnabled = false;
|
||||
}
|
||||
auto uncheckedSendFilesWay = static_cast<SendFilesWay>(sendFilesWay);
|
||||
switch (uncheckedSendFilesWay) {
|
||||
case SendFilesWay::Album:
|
||||
case SendFilesWay::Photos:
|
||||
case SendFilesWay::Files: _variables.sendFilesWay = uncheckedSendFilesWay;
|
||||
}
|
||||
}
|
||||
|
||||
void AuthSessionData::markItemLayoutChanged(not_null<const HistoryItem*> item) {
|
||||
@@ -242,13 +255,12 @@ auto AuthSessionData::megagroupParticipantRemoved() const -> rpl::producer<Megag
|
||||
|
||||
rpl::producer<not_null<UserData*>> AuthSessionData::megagroupParticipantRemoved(
|
||||
not_null<ChannelData*> channel) const {
|
||||
return megagroupParticipantRemoved()
|
||||
| rpl::filter([channel](auto updateChannel, auto user) {
|
||||
return (updateChannel == channel);
|
||||
})
|
||||
| rpl::map([](auto updateChannel, auto user) {
|
||||
return user;
|
||||
});
|
||||
return megagroupParticipantRemoved(
|
||||
) | rpl::filter([channel](auto updateChannel, auto user) {
|
||||
return (updateChannel == channel);
|
||||
}) | rpl::map([](auto updateChannel, auto user) {
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
void AuthSessionData::addNewMegagroupParticipant(
|
||||
@@ -263,13 +275,12 @@ auto AuthSessionData::megagroupParticipantAdded() const -> rpl::producer<Megagro
|
||||
|
||||
rpl::producer<not_null<UserData*>> AuthSessionData::megagroupParticipantAdded(
|
||||
not_null<ChannelData*> channel) const {
|
||||
return megagroupParticipantAdded()
|
||||
| rpl::filter([channel](auto updateChannel, auto user) {
|
||||
return (updateChannel == channel);
|
||||
})
|
||||
| rpl::map([](auto updateChannel, auto user) {
|
||||
return user;
|
||||
});
|
||||
return megagroupParticipantAdded(
|
||||
) | rpl::filter([channel](auto updateChannel, auto user) {
|
||||
return (updateChannel == channel);
|
||||
}) | rpl::map([](auto updateChannel, auto user) {
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
void AuthSessionData::setTabbedSelectorSectionEnabled(bool enabled) {
|
||||
@@ -378,6 +389,13 @@ MessageIdsList AuthSessionData::itemsToIds(
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
MessageIdsList AuthSessionData::groupToIds(
|
||||
not_null<HistoryMessageGroup*> group) const {
|
||||
auto result = itemsToIds(group->others);
|
||||
result.push_back(group->leader->fullId());
|
||||
return result;
|
||||
}
|
||||
|
||||
AuthSession &Auth() {
|
||||
auto result = Messenger::Instance().authSession();
|
||||
Assert(result != nullptr);
|
||||
@@ -392,8 +410,10 @@ AuthSession::AuthSession(UserId userId)
|
||||
, _downloader(std::make_unique<Storage::Downloader>())
|
||||
, _uploader(std::make_unique<Storage::Uploader>())
|
||||
, _storage(std::make_unique<Storage::Facade>())
|
||||
, _notifications(std::make_unique<Window::Notifications::System>(this)) {
|
||||
, _notifications(std::make_unique<Window::Notifications::System>(this))
|
||||
, _changelogs(Core::Changelogs::Create(this)) {
|
||||
Expects(_userId != 0);
|
||||
|
||||
_saveDataTimer.setCallback([this] {
|
||||
Local::writeUserSettings();
|
||||
});
|
||||
@@ -401,7 +421,7 @@ AuthSession::AuthSession(UserId userId)
|
||||
_shouldLockAt = 0;
|
||||
notifications().updateAll();
|
||||
});
|
||||
_api->start();
|
||||
Window::Theme::Background()->start();
|
||||
}
|
||||
|
||||
bool AuthSession::Exists() {
|
||||
|
||||
@@ -26,6 +26,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "base/timer.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
|
||||
class ApiWrap;
|
||||
enum class SendFilesWay;
|
||||
|
||||
namespace Storage {
|
||||
class Downloader;
|
||||
class Uploader;
|
||||
@@ -47,7 +50,9 @@ namespace ChatHelpers {
|
||||
enum class SelectorTab;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
class ApiWrap;
|
||||
namespace Core {
|
||||
class Changelogs;
|
||||
} // namespace Core
|
||||
|
||||
class AuthSessionData final {
|
||||
public:
|
||||
@@ -108,6 +113,12 @@ public:
|
||||
void setLastSeenWarningSeen(bool lastSeenWarningSeen) {
|
||||
_variables.lastSeenWarningSeen = lastSeenWarningSeen;
|
||||
}
|
||||
void setSendFilesWay(SendFilesWay way) {
|
||||
_variables.sendFilesWay = way;
|
||||
}
|
||||
SendFilesWay sendFilesWay() const {
|
||||
return _variables.sendFilesWay;
|
||||
}
|
||||
ChatHelpers::SelectorTab selectorTab() const {
|
||||
return _variables.selectorTab;
|
||||
}
|
||||
@@ -263,6 +274,7 @@ public:
|
||||
|
||||
HistoryItemsList idsToItems(const MessageIdsList &ids) const;
|
||||
MessageIdsList itemsToIds(const HistoryItemsList &items) const;
|
||||
MessageIdsList groupToIds(not_null<HistoryMessageGroup*> group) const;
|
||||
|
||||
private:
|
||||
struct Variables {
|
||||
@@ -272,6 +284,7 @@ private:
|
||||
static constexpr auto kDefaultThirdColumnWidth = 0;
|
||||
|
||||
bool lastSeenWarningSeen = false;
|
||||
SendFilesWay sendFilesWay;
|
||||
ChatHelpers::SelectorTab selectorTab; // per-window
|
||||
bool tabbedSelectorSectionEnabled = false; // per-window
|
||||
int tabbedSelectorSectionTooltipShown = 0;
|
||||
@@ -405,5 +418,6 @@ private:
|
||||
const std::unique_ptr<Storage::Uploader> _uploader;
|
||||
const std::unique_ptr<Storage::Facade> _storage;
|
||||
const std::unique_ptr<Window::Notifications::System> _notifications;
|
||||
const std::unique_ptr<Core::Changelogs> _changelogs;
|
||||
|
||||
};
|
||||
|
||||
@@ -46,7 +46,6 @@ struct SubscriptionHandlerHelper<void> {
|
||||
template <typename EventType>
|
||||
using SubscriptionHandler = typename SubscriptionHandlerHelper<EventType>::type;
|
||||
|
||||
// Required because QShared/WeakPointer can't point to void.
|
||||
class BaseObservableData {
|
||||
};
|
||||
|
||||
@@ -84,14 +83,17 @@ public:
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
Node(const QSharedPointer<internal::BaseObservableData> &observable) : observable(observable) {
|
||||
Node(const std::shared_ptr<internal::BaseObservableData> &observable)
|
||||
: observable(observable) {
|
||||
}
|
||||
Node *next = nullptr;
|
||||
Node *prev = nullptr;
|
||||
QWeakPointer<internal::BaseObservableData> observable;
|
||||
std::weak_ptr<internal::BaseObservableData> observable;
|
||||
};
|
||||
using RemoveAndDestroyMethod = void(*)(Node*);
|
||||
Subscription(Node *node, RemoveAndDestroyMethod removeAndDestroyMethod) : _node(node), _removeAndDestroyMethod(removeAndDestroyMethod) {
|
||||
Subscription(Node *node, RemoveAndDestroyMethod removeAndDestroyMethod)
|
||||
: _node(node)
|
||||
, _removeAndDestroyMethod(removeAndDestroyMethod) {
|
||||
}
|
||||
|
||||
Node *_node = nullptr;
|
||||
@@ -115,13 +117,13 @@ class CommonObservable {
|
||||
public:
|
||||
Subscription add_subscription(Handler &&handler) {
|
||||
if (!_data) {
|
||||
_data = MakeShared<ObservableData<EventType, Handler>>(this);
|
||||
_data = std::make_shared<ObservableData<EventType, Handler>>(this);
|
||||
}
|
||||
return _data->append(std::move(handler));
|
||||
}
|
||||
|
||||
private:
|
||||
QSharedPointer<ObservableData<EventType, Handler>> _data;
|
||||
std::shared_ptr<ObservableData<EventType, Handler>> _data;
|
||||
|
||||
friend class CommonObservableData<EventType, Handler>;
|
||||
friend class BaseObservable<EventType, Handler, base::type_traits<EventType>::is_fast_copy_type::value>;
|
||||
@@ -184,7 +186,11 @@ public:
|
||||
|
||||
private:
|
||||
struct Node : public Subscription::Node {
|
||||
Node(const QSharedPointer<BaseObservableData> &observer, Handler &&handler) : Subscription::Node(observer), handler(std::move(handler)) {
|
||||
Node(
|
||||
const std::shared_ptr<BaseObservableData> &observer,
|
||||
Handler &&handler)
|
||||
: Subscription::Node(observer)
|
||||
, handler(std::move(handler)) {
|
||||
}
|
||||
Handler handler;
|
||||
};
|
||||
@@ -210,8 +216,8 @@ private:
|
||||
}
|
||||
|
||||
static void removeAndDestroyNode(Subscription::Node *node) {
|
||||
if (auto that = node->observable.toStrongRef()) {
|
||||
static_cast<CommonObservableData*>(that.data())->remove(node);
|
||||
if (const auto that = node->observable.lock()) {
|
||||
static_cast<CommonObservableData*>(that.get())->remove(node);
|
||||
}
|
||||
delete static_cast<Node*>(node);
|
||||
}
|
||||
|
||||
@@ -1,393 +0,0 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "base/task_queue.h"
|
||||
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
|
||||
namespace base {
|
||||
namespace {
|
||||
|
||||
auto MainThreadId = std::this_thread::get_id();
|
||||
const auto MaxThreadsCount = qMax(std::thread::hardware_concurrency(), 2U);
|
||||
|
||||
} // namespace
|
||||
|
||||
class TaskQueue::TaskQueueList {
|
||||
public:
|
||||
TaskQueueList();
|
||||
|
||||
void Register(TaskQueue *queue);
|
||||
void Unregister(TaskQueue *queue);
|
||||
bool IsInList(TaskQueue *queue) const;
|
||||
void Clear();
|
||||
bool Empty(int list_index_) const;
|
||||
TaskQueue *TakeFirst(int list_index_);
|
||||
|
||||
private:
|
||||
void Insert(TaskQueue *queue, int list_index_);
|
||||
void Remove(TaskQueue *queue, int list_index_);
|
||||
|
||||
TaskQueue *Tail() { return &tail_; }
|
||||
const TaskQueue *Tail() const { return &tail_; }
|
||||
|
||||
TaskQueue tail_ = { Type::Special, Priority::Normal };
|
||||
TaskQueue *(lists_[kQueuesListsCount]);
|
||||
|
||||
};
|
||||
|
||||
class TaskQueue::TaskThreadPool {
|
||||
struct Private {
|
||||
};
|
||||
|
||||
public:
|
||||
TaskThreadPool(const Private &) { }
|
||||
static const std::shared_ptr<TaskThreadPool> &Instance();
|
||||
|
||||
void AddQueueTask(TaskQueue *queue, Task &&task);
|
||||
void RemoveQueue(TaskQueue *queue);
|
||||
|
||||
~TaskThreadPool();
|
||||
|
||||
private:
|
||||
void ThreadFunction();
|
||||
|
||||
std::vector<std::thread> threads_;
|
||||
QMutex queues_mutex_;
|
||||
|
||||
// queues_mutex_ must be locked when working with the list.
|
||||
TaskQueueList queue_list_;
|
||||
|
||||
QWaitCondition thread_condition_;
|
||||
bool stopped_ = false;
|
||||
int tasks_in_process_ = 0;
|
||||
int background_tasks_in_process_ = 0;
|
||||
|
||||
};
|
||||
|
||||
TaskQueue::TaskQueueList::TaskQueueList() {
|
||||
for (auto &list : lists_) {
|
||||
list = &tail_;
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::TaskQueueList::Register(TaskQueue *queue) {
|
||||
Assert(!queue->SerialTaskInProcess());
|
||||
|
||||
Insert(queue, kAllQueuesList);
|
||||
if (queue->priority_ == Priority::Normal) {
|
||||
Insert(queue, kOnlyNormalQueuesList);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::TaskQueueList::Unregister(TaskQueue *queue) {
|
||||
Remove(queue, kAllQueuesList);
|
||||
if (queue->priority_ == Priority::Normal) {
|
||||
Remove(queue, kOnlyNormalQueuesList);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::TaskQueueList::Insert(TaskQueue *queue, int list_index_) {
|
||||
Assert(list_index_ < kQueuesListsCount);
|
||||
|
||||
auto tail = Tail();
|
||||
if (lists_[list_index_] == tail) {
|
||||
lists_[list_index_] = queue;
|
||||
}
|
||||
|
||||
auto &list_entry = queue->list_entries_[list_index_];
|
||||
Assert(list_entry.after == nullptr);
|
||||
if ((list_entry.before = tail->list_entries_[list_index_].before)) {
|
||||
list_entry.before->list_entries_[list_index_].after = queue;
|
||||
}
|
||||
list_entry.after = tail;
|
||||
tail->list_entries_[list_index_].before = queue;
|
||||
}
|
||||
|
||||
void TaskQueue::TaskQueueList::Remove(TaskQueue *queue, int list_index_) {
|
||||
Assert(list_index_ < kQueuesListsCount);
|
||||
|
||||
auto &list_entry = queue->list_entries_[list_index_];
|
||||
Assert(list_entry.after != nullptr);
|
||||
if (lists_[list_index_] == queue) {
|
||||
lists_[list_index_] = list_entry.after;
|
||||
} else {
|
||||
Assert(list_entry.before != nullptr);
|
||||
list_entry.before->list_entries_[list_index_].after = list_entry.after;
|
||||
}
|
||||
list_entry.after->list_entries_[list_index_].before = list_entry.before;
|
||||
list_entry.before = list_entry.after = nullptr;
|
||||
}
|
||||
|
||||
bool TaskQueue::TaskQueueList::IsInList(TaskQueue *queue) const {
|
||||
if (queue->list_entries_[kAllQueuesList].after) {
|
||||
return true;
|
||||
}
|
||||
Assert(queue->list_entries_[kOnlyNormalQueuesList].after == nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
void TaskQueue::TaskQueueList::Clear() {
|
||||
auto tail = Tail();
|
||||
for (int i = 0; i < kQueuesListsCount; ++i) {
|
||||
for (auto j = lists_[i], next = j; j != tail; j = next) {
|
||||
auto &list_entry = j->list_entries_[i];
|
||||
next = list_entry.after;
|
||||
list_entry.before = list_entry.after = nullptr;
|
||||
}
|
||||
lists_[i] = tail;
|
||||
}
|
||||
}
|
||||
|
||||
bool TaskQueue::TaskQueueList::Empty(int list_index_) const {
|
||||
Assert(list_index_ < kQueuesListsCount);
|
||||
|
||||
auto list = lists_[list_index_];
|
||||
Assert(list != nullptr);
|
||||
return (list->list_entries_[list_index_].after == nullptr);
|
||||
}
|
||||
|
||||
TaskQueue *TaskQueue::TaskQueueList::TakeFirst(int list_index_) {
|
||||
Assert(!Empty(list_index_));
|
||||
|
||||
auto queue = lists_[list_index_];
|
||||
Unregister(queue);
|
||||
// log_msgs.push_back("Unregistered from list in TakeFirst");
|
||||
return queue;
|
||||
}
|
||||
|
||||
void TaskQueue::TaskThreadPool::AddQueueTask(TaskQueue *queue, Task &&task) {
|
||||
QMutexLocker lock(&queues_mutex_);
|
||||
|
||||
queue->tasks_.push_back(std::move(task));
|
||||
auto list_was_empty = queue_list_.Empty(kAllQueuesList);
|
||||
auto threads_count = threads_.size();
|
||||
auto all_threads_processing = (threads_count == tasks_in_process_);
|
||||
auto some_threads_are_vacant = !all_threads_processing && list_was_empty;
|
||||
auto will_create_thread = !some_threads_are_vacant && (threads_count < MaxThreadsCount);
|
||||
|
||||
if (!queue->SerialTaskInProcess()) {
|
||||
if (!queue_list_.IsInList(queue)) {
|
||||
queue_list_.Register(queue);
|
||||
}
|
||||
}
|
||||
if (will_create_thread) {
|
||||
threads_.emplace_back([this]() {
|
||||
ThreadFunction();
|
||||
});
|
||||
} else if (some_threads_are_vacant) {
|
||||
Assert(threads_count > tasks_in_process_);
|
||||
thread_condition_.wakeOne();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::TaskThreadPool::RemoveQueue(TaskQueue *queue) {
|
||||
QMutexLocker lock(&queues_mutex_);
|
||||
if (queue_list_.IsInList(queue)) {
|
||||
queue_list_.Unregister(queue);
|
||||
}
|
||||
if (queue->destroyed_flag_) {
|
||||
*queue->destroyed_flag_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
TaskQueue::TaskThreadPool::~TaskThreadPool() {
|
||||
{
|
||||
QMutexLocker lock(&queues_mutex_);
|
||||
queue_list_.Clear();
|
||||
stopped_ = true;
|
||||
}
|
||||
thread_condition_.wakeAll();
|
||||
for (auto &thread : threads_) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
const std::shared_ptr<TaskQueue::TaskThreadPool> &TaskQueue::TaskThreadPool::Instance() { // static
|
||||
static auto Pool = std::make_shared<TaskThreadPool>(Private());
|
||||
return Pool;
|
||||
}
|
||||
|
||||
void TaskQueue::TaskThreadPool::ThreadFunction() {
|
||||
// Flag marking that the previous processed task was
|
||||
// with a Background priority. We count all the background
|
||||
// tasks being processed.
|
||||
bool background_task = false;
|
||||
|
||||
// Saved serial queue pointer. When we process a serial
|
||||
// queue task we don't return the queue to the list until
|
||||
// the task is processed and we return it on the next cycle.
|
||||
TaskQueue *serial_queue = nullptr;
|
||||
bool serial_queue_destroyed = false;
|
||||
bool task_was_processed = false;
|
||||
while (true) {
|
||||
Task task;
|
||||
{
|
||||
QMutexLocker lock(&queues_mutex_);
|
||||
|
||||
// Finish the previous task processing.
|
||||
if (task_was_processed) {
|
||||
--tasks_in_process_;
|
||||
}
|
||||
if (background_task) {
|
||||
--background_tasks_in_process_;
|
||||
background_task = false;
|
||||
}
|
||||
if (serial_queue) {
|
||||
if (!serial_queue_destroyed) {
|
||||
serial_queue->destroyed_flag_ = nullptr;
|
||||
if (!serial_queue->tasks_.empty()) {
|
||||
queue_list_.Register(serial_queue);
|
||||
}
|
||||
}
|
||||
serial_queue = nullptr;
|
||||
serial_queue_destroyed = false;
|
||||
}
|
||||
|
||||
// Wait for a task to appear in the queues list.
|
||||
while (queue_list_.Empty(kAllQueuesList)) {
|
||||
if (stopped_) {
|
||||
return;
|
||||
}
|
||||
thread_condition_.wait(&queues_mutex_);
|
||||
}
|
||||
|
||||
// Select a task we will be processing.
|
||||
auto processing_background = (background_tasks_in_process_ > 0);
|
||||
auto take_only_normal = processing_background && !queue_list_.Empty(kOnlyNormalQueuesList);
|
||||
auto take_from_list_ = take_only_normal ? kOnlyNormalQueuesList : kAllQueuesList;
|
||||
auto queue = queue_list_.TakeFirst(take_from_list_);
|
||||
|
||||
Assert(!queue->tasks_.empty());
|
||||
|
||||
task = std::move(queue->tasks_.front());
|
||||
queue->tasks_.pop_front();
|
||||
|
||||
if (queue->type_ == Type::Serial) {
|
||||
// Serial queues are returned in the list for processing
|
||||
// only after the task is finished.
|
||||
serial_queue = queue;
|
||||
Assert(serial_queue->destroyed_flag_ == nullptr);
|
||||
serial_queue->destroyed_flag_ = &serial_queue_destroyed;
|
||||
} else if (!queue->tasks_.empty()) {
|
||||
queue_list_.Register(queue);
|
||||
}
|
||||
|
||||
++tasks_in_process_;
|
||||
task_was_processed = true;
|
||||
if (queue->priority_ == Priority::Background) {
|
||||
++background_tasks_in_process_;
|
||||
background_task = true;
|
||||
}
|
||||
}
|
||||
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
TaskQueue::TaskQueue(Type type, Priority priority)
|
||||
: type_(type)
|
||||
, priority_(priority) {
|
||||
if (type_ != Type::Main && type_ != Type::Special) {
|
||||
weak_thread_pool_ = TaskThreadPool::Instance();
|
||||
}
|
||||
}
|
||||
|
||||
TaskQueue::~TaskQueue() {
|
||||
if (type_ != Type::Main && type_ != Type::Special) {
|
||||
if (auto thread_pool = weak_thread_pool_.lock()) {
|
||||
thread_pool->RemoveQueue(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::Put(Task &&task) {
|
||||
if (type_ == Type::Main) {
|
||||
QMutexLocker lock(&tasks_mutex_);
|
||||
tasks_.push_back(std::move(task));
|
||||
|
||||
Sandbox::MainThreadTaskAdded();
|
||||
} else {
|
||||
Assert(type_ != Type::Special);
|
||||
TaskThreadPool::Instance()->AddQueueTask(this, std::move(task));
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::ProcessMainTasks() { // static
|
||||
Assert(std::this_thread::get_id() == MainThreadId);
|
||||
|
||||
while (ProcessOneMainTask()) {
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::ProcessMainTasks(TimeMs max_time_spent) { // static
|
||||
Assert(std::this_thread::get_id() == MainThreadId);
|
||||
|
||||
auto start_time = getms();
|
||||
while (ProcessOneMainTask()) {
|
||||
if (getms() >= start_time + max_time_spent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TaskQueue::ProcessOneMainTask() { // static
|
||||
Task task;
|
||||
{
|
||||
QMutexLocker lock(&Main().tasks_mutex_);
|
||||
auto &tasks = Main().tasks_;
|
||||
if (tasks.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
task = std::move(tasks.front());
|
||||
tasks.pop_front();
|
||||
}
|
||||
|
||||
task();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TaskQueue::IsMyThread() const {
|
||||
if (type_ == Type::Main) {
|
||||
return (std::this_thread::get_id() == MainThreadId);
|
||||
}
|
||||
Assert(type_ != Type::Special);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default queues.
|
||||
TaskQueue &TaskQueue::Main() { // static
|
||||
static TaskQueue MainQueue { Type::Main, Priority::Normal };
|
||||
return MainQueue;
|
||||
}
|
||||
|
||||
TaskQueue &TaskQueue::Normal() { // static
|
||||
static TaskQueue NormalQueue { Type::Concurrent, Priority::Normal };
|
||||
return NormalQueue;
|
||||
}
|
||||
|
||||
TaskQueue &TaskQueue::Background() { // static
|
||||
static TaskQueue BackgroundQueue { Type::Concurrent, Priority::Background };
|
||||
return BackgroundQueue;
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace base {
|
||||
|
||||
using Task = lambda_once<void()>;
|
||||
|
||||
// An attempt to create/use a TaskQueue or one of the default queues
|
||||
// after the main() has returned leads to an undefined behaviour.
|
||||
class TaskQueue {
|
||||
enum class Type {
|
||||
Main, // Unique queue for main thread tasks.
|
||||
Serial,
|
||||
Concurrent,
|
||||
Special, // Unique special queue for thread pool lists terminal item.
|
||||
};
|
||||
|
||||
public:
|
||||
enum class Priority {
|
||||
Normal,
|
||||
Background,
|
||||
};
|
||||
|
||||
// Creating custom serial queues.
|
||||
TaskQueue(Priority priority) : TaskQueue(Type::Serial, priority) {
|
||||
}
|
||||
|
||||
// Default main and two concurrent queues.
|
||||
static TaskQueue &Main();
|
||||
static TaskQueue &Normal();
|
||||
static TaskQueue &Background();
|
||||
|
||||
void Put(Task &&task);
|
||||
|
||||
static void ProcessMainTasks();
|
||||
static void ProcessMainTasks(TimeMs max_time_spent);
|
||||
|
||||
~TaskQueue();
|
||||
|
||||
private:
|
||||
static bool ProcessOneMainTask();
|
||||
|
||||
TaskQueue(Type type, Priority priority);
|
||||
|
||||
bool IsMyThread() const;
|
||||
bool SerialTaskInProcess() const {
|
||||
return (destroyed_flag_ != nullptr);
|
||||
}
|
||||
|
||||
const Type type_;
|
||||
const Priority priority_;
|
||||
|
||||
std::deque<Task> tasks_;
|
||||
QMutex tasks_mutex_; // Only for the main queue.
|
||||
|
||||
// Only for the other queues, not main.
|
||||
class TaskThreadPool;
|
||||
std::weak_ptr<TaskThreadPool> weak_thread_pool_;
|
||||
|
||||
class TaskQueueList;
|
||||
|
||||
struct TaskQueueListEntry {
|
||||
TaskQueue *before = nullptr;
|
||||
TaskQueue *after = nullptr;
|
||||
};
|
||||
|
||||
// Thread pool queues linked list.
|
||||
static constexpr int kAllQueuesList = 0;
|
||||
|
||||
// Thread pool queues linked list with excluded Background queues.
|
||||
static constexpr int kOnlyNormalQueuesList = 1;
|
||||
|
||||
static constexpr int kQueuesListsCount = 2;
|
||||
TaskQueueListEntry list_entries_[kQueuesListsCount];
|
||||
|
||||
// Only for Serial queues: non-null value means a task is currently processed.
|
||||
bool *destroyed_flag_ = nullptr;
|
||||
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
@@ -280,6 +280,55 @@ weak_ptr<T> make_weak(const std::weak_ptr<T> &value) {
|
||||
|
||||
} // namespace base
|
||||
|
||||
namespace crl {
|
||||
|
||||
template <typename T, typename Enable>
|
||||
struct guard_traits;
|
||||
|
||||
template <typename T>
|
||||
struct guard_traits<base::weak_ptr<T>, void> {
|
||||
static base::weak_ptr<T> create(const base::weak_ptr<T> &value) {
|
||||
return value;
|
||||
}
|
||||
static base::weak_ptr<T> create(base::weak_ptr<T> &&value) {
|
||||
return std::move(value);
|
||||
}
|
||||
static bool check(const base::weak_ptr<T> &guard) {
|
||||
return guard.get() != nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct guard_traits<
|
||||
T*,
|
||||
std::enable_if_t<
|
||||
std::is_base_of_v<base::has_weak_ptr, std::remove_cv_t<T>>>> {
|
||||
static base::weak_ptr<T> create(T *value) {
|
||||
return value;
|
||||
}
|
||||
static bool check(const base::weak_ptr<T> &guard) {
|
||||
return guard.get() != nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct guard_traits<
|
||||
gsl::not_null<T*>,
|
||||
std::enable_if_t<
|
||||
std::is_base_of_v<base::has_weak_ptr, std::remove_cv_t<T>>>> {
|
||||
static base::weak_ptr<T> create(gsl::not_null<T*> value) {
|
||||
return value.get();
|
||||
}
|
||||
static bool check(const base::weak_ptr<T> &guard) {
|
||||
return guard.get() != nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace crl
|
||||
|
||||
#ifdef QT_VERSION
|
||||
template <typename Lambda>
|
||||
inline void InvokeQueued(const base::has_weak_ptr *context, Lambda &&lambda) {
|
||||
|
||||
@@ -30,6 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
#include "core/click_handler_types.h"
|
||||
|
||||
AboutBox::AboutBox(QWidget *parent)
|
||||
: _version(this, lng_about_version(lt_version, QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString())), st::aboutVersionLink)
|
||||
@@ -43,7 +44,18 @@ void AboutBox::prepare() {
|
||||
|
||||
addButton(langFactory(lng_close), [this] { closeBox(); });
|
||||
|
||||
const auto linkHook = [](const ClickHandlerPtr &link, auto button) {
|
||||
if (const auto url = dynamic_cast<UrlClickHandler*>(link.get())) {
|
||||
url->UrlClickHandler::onClick(button);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
_text3->setRichText(lng_about_text_3(lt_faq_open, qsl("[a href=\"%1\"]").arg(telegramFaqLink()), lt_faq_close, qsl("[/a]")));
|
||||
_text1->setClickHandlerHook(linkHook);
|
||||
_text2->setClickHandlerHook(linkHook);
|
||||
_text3->setClickHandlerHook(linkHook);
|
||||
|
||||
_version->setClickedCallback([this] { showVersionHistory(); });
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ QPixmap BoxContent::grabInnerCache() {
|
||||
auto isBottomShadowVisible = !_bottomShadow->isHidden();
|
||||
if (isTopShadowVisible) _topShadow->setVisible(false);
|
||||
if (isBottomShadowVisible) _bottomShadow->setVisible(false);
|
||||
auto result = myGrab(this, _scroll->geometry());
|
||||
auto result = Ui::GrabWidget(this, _scroll->geometry());
|
||||
if (isTopShadowVisible) _topShadow->setVisible(true);
|
||||
if (isBottomShadowVisible) _bottomShadow->setVisible(true);
|
||||
return result;
|
||||
@@ -250,7 +250,6 @@ void AbstractBox::paintEvent(QPaintEvent *e) {
|
||||
auto paintBottomRounded = clip.intersects(QRect(0, height() - st::boxRadius, width(), st::boxRadius));
|
||||
if (paintTopRounded || paintBottomRounded) {
|
||||
auto parts = RectPart::None | 0;
|
||||
parts |= RectPart::None;
|
||||
if (paintTopRounded) parts |= RectPart::FullTop;
|
||||
if (paintBottomRounded) parts |= RectPart::FullBottom;
|
||||
App::roundRect(p, rect(), st::boxBg, BoxCorners, nullptr, parts);
|
||||
|
||||
@@ -36,6 +36,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/special_buttons.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -1378,8 +1379,15 @@ RevokePublicLinkBox::Inner::Inner(QWidget *parent, base::lambda<void()> revokeCa
|
||||
|
||||
auto row = ChatRow(peer);
|
||||
row.peer = peer;
|
||||
row.name.setText(st::contactsNameStyle, peer->name, _textNameOptions);
|
||||
row.status.setText(st::defaultTextStyle, Messenger::Instance().createInternalLink(textcmdLink(1, peer->userName())), _textDlgOptions);
|
||||
row.name.setText(
|
||||
st::contactsNameStyle,
|
||||
peer->name,
|
||||
Ui::NameTextOptions());
|
||||
row.status.setText(
|
||||
st::defaultTextStyle,
|
||||
Messenger::Instance().createInternalLink(
|
||||
textcmdLink(1, peer->userName())),
|
||||
Ui::DialogTextOptions());
|
||||
_rows.push_back(std::move(row));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -716,3 +716,11 @@ groupStickersField: InputField(contactsSearchField) {
|
||||
heightMin: 32px;
|
||||
}
|
||||
groupStickersSubTitleHeight: 36px;
|
||||
|
||||
sendMediaPreviewSize: 308px;
|
||||
sendMediaPreviewHeightMax: 1280;
|
||||
sendMediaPreviewPhotoSkip: 10px;
|
||||
sendMediaFileThumbSize: 64px;
|
||||
sendMediaFileThumbSkip: 10px;
|
||||
sendMediaFileNameTop: 7px;
|
||||
sendMediaFileStatusTop: 37px;
|
||||
|
||||
@@ -327,7 +327,7 @@ void ConvertToSupergroupBox::convertDone(const MTPUpdates &updates) {
|
||||
auto handleChats = [](auto &mtpChats) {
|
||||
for_const (auto &mtpChat, mtpChats.v) {
|
||||
if (mtpChat.type() == mtpc_channel) {
|
||||
auto channel = App::channel(mtpChat.c_channel().vid.v);
|
||||
const auto channel = App::channel(mtpChat.c_channel().vid.v);
|
||||
Ui::showPeerHistory(channel, ShowAtUnreadMsgId);
|
||||
Auth().api().requestParticipantsCountDelayed(channel);
|
||||
}
|
||||
@@ -565,13 +565,22 @@ void DeleteMessagesBox::deleteAndClear() {
|
||||
|
||||
if (_moderateFrom) {
|
||||
if (_banUser && _banUser->checked()) {
|
||||
Auth().api().kickParticipant(_moderateInChannel, _moderateFrom, MTP_channelBannedRights(MTP_flags(0), MTP_int(0)));
|
||||
Auth().api().kickParticipant(
|
||||
_moderateInChannel,
|
||||
_moderateFrom,
|
||||
MTP_channelBannedRights(MTP_flags(0), MTP_int(0)));
|
||||
}
|
||||
if (_reportSpam->checked()) {
|
||||
MTP::send(MTPchannels_ReportSpam(_moderateInChannel->inputChannel, _moderateFrom->inputUser, MTP_vector<MTPint>(1, MTP_int(_ids[0].msg))));
|
||||
MTP::send(
|
||||
MTPchannels_ReportSpam(
|
||||
_moderateInChannel->inputChannel,
|
||||
_moderateFrom->inputUser,
|
||||
MTP_vector<MTPint>(1, MTP_int(_ids[0].msg))));
|
||||
}
|
||||
if (_deleteAll && _deleteAll->checked()) {
|
||||
App::main()->deleteAllFromUser(_moderateInChannel, _moderateFrom);
|
||||
App::main()->deleteAllFromUser(
|
||||
_moderateInChannel,
|
||||
_moderateFrom);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
413
Telegram/SourceFiles/boxes/edit_caption_box.cpp
Normal file
413
Telegram/SourceFiles/boxes/edit_caption_box.cpp
Normal file
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "boxes/edit_caption_box.h"
|
||||
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "mainwidget.h"
|
||||
#include "styles/style_history.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
EditCaptionBox::EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<HistoryMedia*> media,
|
||||
FullMsgId msgId)
|
||||
: _msgId(msgId) {
|
||||
Expects(media->canEditCaption());
|
||||
|
||||
QSize dimensions;
|
||||
ImagePtr image;
|
||||
QString caption;
|
||||
DocumentData *doc = nullptr;
|
||||
|
||||
switch (media->type()) {
|
||||
case MediaTypeGif: {
|
||||
_animated = true;
|
||||
doc = static_cast<HistoryGif*>(media.get())->getDocument();
|
||||
dimensions = doc->dimensions;
|
||||
image = doc->thumb;
|
||||
} break;
|
||||
|
||||
case MediaTypePhoto: {
|
||||
_photo = true;
|
||||
auto photo = static_cast<HistoryPhoto*>(media.get())->getPhoto();
|
||||
dimensions = QSize(photo->full->width(), photo->full->height());
|
||||
image = photo->full;
|
||||
} break;
|
||||
|
||||
case MediaTypeVideo: {
|
||||
_animated = true;
|
||||
doc = static_cast<HistoryVideo*>(media.get())->getDocument();
|
||||
dimensions = doc->dimensions;
|
||||
image = doc->thumb;
|
||||
} break;
|
||||
|
||||
case MediaTypeGrouped: {
|
||||
if (const auto photo = media->getPhoto()) {
|
||||
dimensions = QSize(photo->full->width(), photo->full->height());
|
||||
image = photo->full;
|
||||
_photo = true;
|
||||
} else if (const auto doc = media->getDocument()) {
|
||||
dimensions = doc->dimensions;
|
||||
image = doc->thumb;
|
||||
_animated = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case MediaTypeFile:
|
||||
case MediaTypeMusicFile:
|
||||
case MediaTypeVoiceFile: {
|
||||
_doc = true;
|
||||
doc = static_cast<HistoryDocument*>(media.get())->getDocument();
|
||||
image = doc->thumb;
|
||||
} break;
|
||||
}
|
||||
caption = media->getCaption().text;
|
||||
|
||||
if (!_animated && (dimensions.isEmpty() || doc || image->isNull())) {
|
||||
if (image->isNull()) {
|
||||
_thumbw = 0;
|
||||
} else {
|
||||
int32 tw = image->width(), th = image->height();
|
||||
if (tw > th) {
|
||||
_thumbw = (tw * st::msgFileThumbSize) / th;
|
||||
} else {
|
||||
_thumbw = st::msgFileThumbSize;
|
||||
}
|
||||
auto options = Images::Option::Smooth | Images::Option::RoundedSmall | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::RoundedBottomLeft | Images::Option::RoundedBottomRight;
|
||||
_thumb = Images::pixmap(image->pix().toImage(), _thumbw * cIntRetinaFactor(), 0, options, st::msgFileThumbSize, st::msgFileThumbSize);
|
||||
}
|
||||
|
||||
if (doc) {
|
||||
auto nameString = doc->isVoiceMessage()
|
||||
? lang(lng_media_audio)
|
||||
: doc->composeNameString();
|
||||
_name.setText(
|
||||
st::semiboldTextStyle,
|
||||
nameString,
|
||||
Ui::NameTextOptions());
|
||||
_status = formatSizeText(doc->size);
|
||||
_statusw = qMax(
|
||||
_name.maxWidth(),
|
||||
st::normalFont->width(_status));
|
||||
_isImage = doc->isImage();
|
||||
_isAudio = (doc->isVoiceMessage() || doc->isAudioFile());
|
||||
}
|
||||
} else {
|
||||
int32 maxW = 0, maxH = 0;
|
||||
if (_animated) {
|
||||
int32 limitW = st::sendMediaPreviewSize;
|
||||
int32 limitH = st::confirmMaxHeight;
|
||||
maxW = qMax(dimensions.width(), 1);
|
||||
maxH = qMax(dimensions.height(), 1);
|
||||
if (maxW * limitH > maxH * limitW) {
|
||||
if (maxW < limitW) {
|
||||
maxH = maxH * limitW / maxW;
|
||||
maxW = limitW;
|
||||
}
|
||||
} else {
|
||||
if (maxH < limitH) {
|
||||
maxW = maxW * limitH / maxH;
|
||||
maxH = limitH;
|
||||
}
|
||||
}
|
||||
_thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), Images::Option::Smooth | Images::Option::Blurred, maxW, maxH);
|
||||
prepareGifPreview(doc);
|
||||
} else {
|
||||
maxW = dimensions.width();
|
||||
maxH = dimensions.height();
|
||||
_thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), Images::Option::Smooth, maxW, maxH);
|
||||
}
|
||||
int32 tw = _thumb.width(), th = _thumb.height();
|
||||
if (!tw || !th) {
|
||||
tw = th = 1;
|
||||
}
|
||||
_thumbw = st::sendMediaPreviewSize;
|
||||
if (_thumb.width() < _thumbw) {
|
||||
_thumbw = (_thumb.width() > 20) ? _thumb.width() : 20;
|
||||
}
|
||||
int32 maxthumbh = qMin(qRound(1.5 * _thumbw), int(st::confirmMaxHeight));
|
||||
_thumbh = qRound(th * float64(_thumbw) / tw);
|
||||
if (_thumbh > maxthumbh) {
|
||||
_thumbw = qRound(_thumbw * float64(maxthumbh) / _thumbh);
|
||||
_thumbh = maxthumbh;
|
||||
if (_thumbw < 10) {
|
||||
_thumbw = 10;
|
||||
}
|
||||
}
|
||||
_thumbx = (st::boxWideWidth - _thumbw) / 2;
|
||||
|
||||
_thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
_thumb.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
Assert(_animated || _photo || _doc);
|
||||
|
||||
_field.create(this, st::confirmCaptionArea, langFactory(lng_photo_caption), caption);
|
||||
_field->setMaxLength(MaxPhotoCaption);
|
||||
_field->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
|
||||
}
|
||||
|
||||
void EditCaptionBox::prepareGifPreview(DocumentData *document) {
|
||||
auto createGifPreview = [document] {
|
||||
return (document && document->isAnimation());
|
||||
};
|
||||
auto createGifPreviewResult = createGifPreview(); // Clang freeze workaround.
|
||||
if (createGifPreviewResult) {
|
||||
_gifPreview = Media::Clip::MakeReader(document, _msgId, [this](Media::Clip::Notification notification) {
|
||||
clipCallback(notification);
|
||||
});
|
||||
if (_gifPreview) _gifPreview->setAutoplay();
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::clipCallback(Media::Clip::Notification notification) {
|
||||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case NotificationReinit: {
|
||||
if (_gifPreview && _gifPreview->state() == State::Error) {
|
||||
_gifPreview.setBad();
|
||||
}
|
||||
|
||||
if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) {
|
||||
auto s = QSize(_thumbw, _thumbh);
|
||||
_gifPreview->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None);
|
||||
}
|
||||
|
||||
update();
|
||||
} break;
|
||||
|
||||
case NotificationRepaint: {
|
||||
if (_gifPreview && !_gifPreview->currentDisplayed()) {
|
||||
update();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::prepare() {
|
||||
addButton(langFactory(lng_settings_save), [this] { save(); });
|
||||
addButton(langFactory(lng_cancel), [this] { closeBox(); });
|
||||
|
||||
updateBoxSize();
|
||||
connect(_field, &Ui::InputArea::submitted, this, [this] { save(); });
|
||||
connect(_field, &Ui::InputArea::cancelled, this, [this] {
|
||||
closeBox();
|
||||
});
|
||||
connect(_field, &Ui::InputArea::resized, this, [this] {
|
||||
captionResized();
|
||||
});
|
||||
|
||||
auto cursor = _field->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
_field->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
void EditCaptionBox::captionResized() {
|
||||
updateBoxSize();
|
||||
resizeEvent(0);
|
||||
update();
|
||||
}
|
||||
|
||||
void EditCaptionBox::updateBoxSize() {
|
||||
auto newHeight = st::boxPhotoPadding.top() + st::boxPhotoCaptionSkip + _field->height() + errorTopSkip() + st::normalFont->height;
|
||||
if (_photo || _animated) {
|
||||
newHeight += _thumbh;
|
||||
} else if (_thumbw) {
|
||||
newHeight += 0 + st::msgFileThumbSize + 0;
|
||||
} else if (_doc) {
|
||||
newHeight += 0 + st::msgFileSize + 0;
|
||||
} else {
|
||||
newHeight += st::boxTitleFont->height;
|
||||
}
|
||||
setDimensions(st::boxWideWidth, newHeight);
|
||||
}
|
||||
|
||||
int EditCaptionBox::errorTopSkip() const {
|
||||
return (st::boxButtonPadding.top() / 2);
|
||||
}
|
||||
|
||||
void EditCaptionBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
|
||||
if (_photo || _animated) {
|
||||
if (_thumbx > st::boxPhotoPadding.left()) {
|
||||
p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _thumbx - st::boxPhotoPadding.left(), _thumbh, st::confirmBg);
|
||||
}
|
||||
if (_thumbx + _thumbw < width() - st::boxPhotoPadding.right()) {
|
||||
p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, _thumbh, st::confirmBg);
|
||||
}
|
||||
if (_gifPreview && _gifPreview->started()) {
|
||||
auto s = QSize(_thumbw, _thumbh);
|
||||
auto paused = controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Layer);
|
||||
auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : getms());
|
||||
p.drawPixmap(_thumbx, st::boxPhotoPadding.top(), frame);
|
||||
} else {
|
||||
p.drawPixmap(_thumbx, st::boxPhotoPadding.top(), _thumb);
|
||||
}
|
||||
if (_animated && !_gifPreview) {
|
||||
QRect inner(_thumbx + (_thumbw - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_thumbh - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::msgDateImgBg);
|
||||
|
||||
{
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.drawEllipse(inner);
|
||||
}
|
||||
|
||||
auto icon = &st::historyFileInPlay;
|
||||
icon->paintInCenter(p, inner);
|
||||
}
|
||||
} else if (_doc) {
|
||||
int32 w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
|
||||
int32 h = _thumbw ? (0 + st::msgFileThumbSize + 0) : (0 + st::msgFileSize + 0);
|
||||
int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0;
|
||||
if (_thumbw) {
|
||||
nameleft = 0 + st::msgFileThumbSize + st::msgFileThumbPadding.right();
|
||||
nametop = st::msgFileThumbNameTop - st::msgFileThumbPadding.top();
|
||||
nameright = 0;
|
||||
statustop = st::msgFileThumbStatusTop - st::msgFileThumbPadding.top();
|
||||
} else {
|
||||
nameleft = 0 + st::msgFileSize + st::msgFilePadding.right();
|
||||
nametop = st::msgFileNameTop - st::msgFilePadding.top();
|
||||
nameright = 0;
|
||||
statustop = st::msgFileStatusTop - st::msgFilePadding.top();
|
||||
}
|
||||
int32 namewidth = w - nameleft - 0;
|
||||
if (namewidth > _statusw) {
|
||||
//w -= (namewidth - _statusw);
|
||||
//namewidth = _statusw;
|
||||
}
|
||||
int32 x = (width() - w) / 2, y = st::boxPhotoPadding.top();
|
||||
|
||||
// App::roundRect(p, x, y, w, h, st::msgInBg, MessageInCorners, &st::msgInShadow);
|
||||
|
||||
if (_thumbw) {
|
||||
QRect rthumb(rtlrect(x + 0, y + 0, st::msgFileThumbSize, st::msgFileThumbSize, width()));
|
||||
p.drawPixmap(rthumb.topLeft(), _thumb);
|
||||
} else {
|
||||
QRect inner(rtlrect(x + 0, y + 0, st::msgFileSize, st::msgFileSize, width()));
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::msgFileInBg);
|
||||
|
||||
{
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.drawEllipse(inner);
|
||||
}
|
||||
|
||||
auto icon = &(_isAudio ? st::historyFileInPlay : _isImage ? st::historyFileInImage : st::historyFileInDocument);
|
||||
icon->paintInCenter(p, inner);
|
||||
}
|
||||
p.setFont(st::semiboldFont);
|
||||
p.setPen(st::historyFileNameInFg);
|
||||
_name.drawLeftElided(p, x + nameleft, y + nametop, namewidth, width());
|
||||
|
||||
auto &status = st::mediaInFg;
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(status);
|
||||
p.drawTextLeft(x + nameleft, y + statustop, width(), _status);
|
||||
} else {
|
||||
p.setFont(st::boxTitleFont);
|
||||
p.setPen(st::boxTextFg);
|
||||
p.drawTextLeft(_field->x(), st::boxPhotoPadding.top(), width(), lang(lng_edit_message));
|
||||
}
|
||||
|
||||
if (!_error.isEmpty()) {
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(st::boxTextFgError);
|
||||
p.drawTextLeft(_field->x(), _field->y() + _field->height() + errorTopSkip(), width(), _error);
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
_field->resize(st::sendMediaPreviewSize, _field->height());
|
||||
_field->moveToLeft(st::boxPhotoPadding.left(), height() - st::normalFont->height - errorTopSkip() - _field->height());
|
||||
}
|
||||
|
||||
void EditCaptionBox::setInnerFocus() {
|
||||
_field->setFocusFast();
|
||||
}
|
||||
|
||||
void EditCaptionBox::save() {
|
||||
if (_saveRequestId) return;
|
||||
|
||||
auto item = App::histItemById(_msgId);
|
||||
if (!item) {
|
||||
_error = lang(lng_edit_deleted);
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
auto flags = MTPmessages_EditMessage::Flag::f_message | 0;
|
||||
if (_previewCancelled) {
|
||||
flags |= MTPmessages_EditMessage::Flag::f_no_webpage;
|
||||
}
|
||||
MTPVector<MTPMessageEntity> sentEntities;
|
||||
if (!sentEntities.v.isEmpty()) {
|
||||
flags |= MTPmessages_EditMessage::Flag::f_entities;
|
||||
}
|
||||
auto text = TextUtilities::PrepareForSending(_field->getLastText(), TextUtilities::PrepareTextOption::CheckLinks);
|
||||
_saveRequestId = MTP::send(
|
||||
MTPmessages_EditMessage(
|
||||
MTP_flags(flags),
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id),
|
||||
MTP_string(text),
|
||||
MTPnullMarkup,
|
||||
sentEntities,
|
||||
MTP_inputGeoPointEmpty()),
|
||||
rpcDone(&EditCaptionBox::saveDone),
|
||||
rpcFail(&EditCaptionBox::saveFail));
|
||||
}
|
||||
|
||||
void EditCaptionBox::saveDone(const MTPUpdates &updates) {
|
||||
_saveRequestId = 0;
|
||||
closeBox();
|
||||
if (App::main()) {
|
||||
App::main()->sentUpdatesReceived(updates);
|
||||
}
|
||||
}
|
||||
|
||||
bool EditCaptionBox::saveFail(const RPCError &error) {
|
||||
if (MTP::isDefaultHandledError(error)) return false;
|
||||
|
||||
_saveRequestId = 0;
|
||||
QString err = error.type();
|
||||
if (err == qstr("MESSAGE_ID_INVALID") || err == qstr("CHAT_ADMIN_REQUIRED") || err == qstr("MESSAGE_EDIT_TIME_EXPIRED")) {
|
||||
_error = lang(lng_edit_error);
|
||||
} else if (err == qstr("MESSAGE_NOT_MODIFIED")) {
|
||||
closeBox();
|
||||
return true;
|
||||
} else if (err == qstr("MESSAGE_EMPTY")) {
|
||||
_field->setFocus();
|
||||
_field->showError();
|
||||
} else {
|
||||
_error = lang(lng_edit_error);
|
||||
}
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
77
Telegram/SourceFiles/boxes/edit_caption_box.h
Normal file
77
Telegram/SourceFiles/boxes/edit_caption_box.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
namespace Ui {
|
||||
class InputArea;
|
||||
} // namespace Ui
|
||||
|
||||
class EditCaptionBox : public BoxContent, public RPCSender {
|
||||
public:
|
||||
EditCaptionBox(QWidget*, not_null<HistoryMedia*> media, FullMsgId msgId);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
void updateBoxSize();
|
||||
void prepareGifPreview(DocumentData *document);
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
|
||||
void save();
|
||||
void captionResized();
|
||||
|
||||
void saveDone(const MTPUpdates &updates);
|
||||
bool saveFail(const RPCError &error);
|
||||
|
||||
int errorTopSkip() const;
|
||||
|
||||
FullMsgId _msgId;
|
||||
bool _animated = false;
|
||||
bool _photo = false;
|
||||
bool _doc = false;
|
||||
|
||||
QPixmap _thumb;
|
||||
Media::Clip::ReaderPointer _gifPreview;
|
||||
|
||||
object_ptr<Ui::InputArea> _field = { nullptr };
|
||||
|
||||
int _thumbx = 0;
|
||||
int _thumbw = 0;
|
||||
int _thumbh = 0;
|
||||
Text _name;
|
||||
QString _status;
|
||||
int _statusw = 0;
|
||||
bool _isAudio = false;
|
||||
bool _isImage = false;
|
||||
|
||||
bool _previewCancelled = false;
|
||||
mtpRequestId _saveRequestId = 0;
|
||||
|
||||
QString _error;
|
||||
|
||||
};
|
||||
@@ -24,9 +24,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "ui/special_buttons.h"
|
||||
#include "boxes/calendar_box.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -127,7 +129,10 @@ EditParticipantBox::Inner::Inner(
|
||||
st::rightsPhotoButton)
|
||||
, _hasAdminRights(hasAdminRights) {
|
||||
_userPhoto->setPointerCursor(false);
|
||||
_userName.setText(st::rightsNameStyle, App::peerName(_user), _textNameOptions);
|
||||
_userName.setText(
|
||||
st::rightsNameStyle,
|
||||
App::peerName(_user),
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
void EditParticipantBox::Inner::removeControl(QPointer<TWidget> widget) {
|
||||
@@ -176,7 +181,7 @@ void EditParticipantBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
auto seesAllMessages = (_user->botInfo->readsAllHistory || _hasAdminRights);
|
||||
return lang(seesAllMessages ? lng_status_bot_reads_all : lng_status_bot_not_reads_all);
|
||||
}
|
||||
return App::onlineText(_user->onlineTill, unixtime());
|
||||
return Data::OnlineText(_user->onlineTill, unixtime());
|
||||
};
|
||||
p.setFont(st::contactsStatusFont);
|
||||
p.setPen(st::contactsStatusFg);
|
||||
|
||||
@@ -287,10 +287,10 @@ void EditPrivacyBox::createWidgets() {
|
||||
};
|
||||
auto createExceptionLink = [this](Exception exception) {
|
||||
exceptionLink(exception).create(this, object_ptr<Ui::LinkButton>(this, exceptionLinkText(exception)), exceptionLinkMargins());
|
||||
exceptionLink(exception)->heightValue()
|
||||
| rpl::start_with_next([this] {
|
||||
resizeToWidth(width());
|
||||
}, lifetime());
|
||||
exceptionLink(exception)->heightValue(
|
||||
) | rpl::start_with_next([this] {
|
||||
resizeToWidth(width());
|
||||
}, lifetime());
|
||||
exceptionLink(exception)->entity()->setClickedCallback([this, exception] { editExceptionUsers(exception); });
|
||||
};
|
||||
|
||||
|
||||
@@ -127,10 +127,10 @@ void NotificationsBox::prepare() {
|
||||
_sampleOpacities.push_back(Animation());
|
||||
}
|
||||
_countSlider->setActiveSectionFast(_oldCount - 1);
|
||||
_countSlider->sectionActivated()
|
||||
| rpl::start_with_next(
|
||||
[this] { countChanged(); },
|
||||
lifetime());
|
||||
_countSlider->sectionActivated(
|
||||
) | rpl::start_with_next(
|
||||
[this] { countChanged(); },
|
||||
lifetime());
|
||||
|
||||
setMouseTracking(true);
|
||||
|
||||
|
||||
@@ -34,9 +34,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "observer_peer.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
|
||||
PeerListBox::PeerListBox(
|
||||
@@ -53,10 +55,10 @@ void PeerListBox::createMultiSelect() {
|
||||
|
||||
auto entity = object_ptr<Ui::MultiSelect>(this, st::contactsMultiSelect, langFactory(lng_participant_filter));
|
||||
_select.create(this, std::move(entity));
|
||||
_select->heightValue()
|
||||
| rpl::start_with_next(
|
||||
[this] { updateScrollSkips(); },
|
||||
lifetime());
|
||||
_select->heightValue(
|
||||
) | rpl::start_with_next(
|
||||
[this] { updateScrollSkips(); },
|
||||
lifetime());
|
||||
_select->entity()->setSubmittedCallback([this](bool chtrlShiftEnter) { content()->submitted(); });
|
||||
_select->entity()->setQueryChangedCallback([this](const QString &query) { searchQueryChanged(query); });
|
||||
_select->entity()->setItemRemovedCallback([this](uint64 itemId) {
|
||||
@@ -103,15 +105,15 @@ void PeerListBox::prepare() {
|
||||
setDimensions(st::boxWideWidth, st::boxMaxListHeight);
|
||||
if (_select) {
|
||||
_select->finishAnimating();
|
||||
myEnsureResized(_select);
|
||||
Ui::SendPendingMoveResizeEvents(_select);
|
||||
_scrollBottomFixed = true;
|
||||
onScrollToY(0);
|
||||
}
|
||||
|
||||
content()->scrollToRequests()
|
||||
| rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
||||
onScrollToY(request.ymin, request.ymax);
|
||||
}, lifetime());
|
||||
content()->scrollToRequests(
|
||||
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
||||
onScrollToY(request.ymin, request.ymax);
|
||||
}, lifetime());
|
||||
|
||||
if (_init) {
|
||||
_init(this);
|
||||
@@ -357,12 +359,12 @@ void PeerListRow::refreshStatus() {
|
||||
setStatusText(lang(lng_saved_forward_here));
|
||||
} else {
|
||||
auto time = unixtime();
|
||||
setStatusText(App::onlineText(user, time));
|
||||
if (App::onlineColorUse(user, time)) {
|
||||
setStatusText(Data::OnlineText(user, time));
|
||||
if (Data::OnlineTextActive(user, time)) {
|
||||
_statusType = StatusType::Online;
|
||||
}
|
||||
_statusValidTill = getms()
|
||||
+ App::onlineWillChangeIn(user, time) * 1000LL;
|
||||
+ Data::OnlineChangeTimeout(user, time);
|
||||
}
|
||||
} else if (auto chat = peer()->asChat()) {
|
||||
if (!chat->amIn()) {
|
||||
@@ -390,7 +392,7 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
|
||||
const auto text = _isSavedMessagesChat
|
||||
? lang(lng_saved_messages)
|
||||
: peer()->name;
|
||||
_name.setText(st.nameStyle, text, _textNameOptions);
|
||||
_name.setText(st.nameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
PeerListRow::~PeerListRow() = default;
|
||||
@@ -515,7 +517,7 @@ void PeerListRow::paintDisabledCheckUserpic(
|
||||
}
|
||||
|
||||
void PeerListRow::setStatusText(const QString &text) {
|
||||
_status.setText(st::defaultTextStyle, text, _textNameOptions);
|
||||
_status.setText(st::defaultTextStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
float64 PeerListRow::checkedRatio() {
|
||||
|
||||
@@ -715,10 +715,10 @@ public:
|
||||
}
|
||||
void peerListSortRows(
|
||||
base::lambda<bool(const PeerListRow &a, const PeerListRow &b)> compare) override {
|
||||
_content->reorderRows([compare = std::move(compare)](
|
||||
_content->reorderRows([&](
|
||||
auto &&begin,
|
||||
auto &&end) {
|
||||
std::sort(begin, end, [&compare](auto &&a, auto &&b) {
|
||||
std::sort(begin, end, [&](auto &&a, auto &&b) {
|
||||
return compare(*a, *b);
|
||||
});
|
||||
});
|
||||
@@ -726,10 +726,10 @@ public:
|
||||
int peerListPartitionRows(
|
||||
base::lambda<bool(const PeerListRow &a)> border) override {
|
||||
auto result = 0;
|
||||
_content->reorderRows([border = std::move(border), &result](
|
||||
_content->reorderRows([&](
|
||||
auto &&begin,
|
||||
auto &&end) {
|
||||
auto edge = std::stable_partition(begin, end, [&border](
|
||||
auto edge = std::stable_partition(begin, end, [&](
|
||||
auto &¤t) {
|
||||
return border(*current);
|
||||
});
|
||||
|
||||
@@ -750,23 +750,24 @@ void AddBotToGroupBoxController::shareBotGame(not_null<PeerData*> chat) {
|
||||
if (!weak) {
|
||||
return;
|
||||
}
|
||||
auto history = App::historyLoaded(chat);
|
||||
auto afterRequestId = history ? history->sendRequestId : 0;
|
||||
auto randomId = rand_value<uint64>();
|
||||
auto gameShortName = bot->botInfo->shareGameShortName;
|
||||
auto inputGame = MTP_inputGameShortName(
|
||||
bot->inputUser,
|
||||
MTP_string(gameShortName));
|
||||
auto request = MTPmessages_SendMedia(
|
||||
MTP_flags(0),
|
||||
chat->input,
|
||||
MTP_int(0),
|
||||
MTP_inputMediaGame(inputGame),
|
||||
MTP_long(randomId),
|
||||
MTPnullMarkup);
|
||||
auto done = App::main()->rpcDone(&MainWidget::sentUpdatesReceived);
|
||||
auto fail = App::main()->rpcFail(&MainWidget::sendMessageFail);
|
||||
auto requestId = MTP::send(request, done, fail, 0, 0, afterRequestId);
|
||||
const auto history = App::historyLoaded(chat);
|
||||
const auto randomId = rand_value<uint64>();
|
||||
const auto requestId = MTP::send(
|
||||
MTPmessages_SendMedia(
|
||||
MTP_flags(0),
|
||||
chat->input,
|
||||
MTP_int(0),
|
||||
MTP_inputMediaGame(
|
||||
MTP_inputGameShortName(
|
||||
bot->inputUser,
|
||||
MTP_string(bot->botInfo->shareGameShortName))),
|
||||
MTP_long(randomId),
|
||||
MTPnullMarkup),
|
||||
App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
|
||||
App::main()->rpcFail(&MainWidget::sendMessageFail),
|
||||
0,
|
||||
0,
|
||||
history ? history->sendRequestId : 0);
|
||||
if (history) {
|
||||
history->sendRequestId = requestId;
|
||||
}
|
||||
@@ -778,9 +779,9 @@ void AddBotToGroupBoxController::shareBotGame(not_null<PeerData*> chat) {
|
||||
return lng_bot_sure_share_game(lt_user, App::peerName(chat));
|
||||
}
|
||||
return lng_bot_sure_share_game_group(lt_group, chat->name);
|
||||
};
|
||||
}();
|
||||
Ui::show(
|
||||
Box<ConfirmBox>(confirmText(), send),
|
||||
Box<ConfirmBox>(confirmText, std::move(send)),
|
||||
LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
@@ -799,17 +800,16 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
|
||||
}
|
||||
if (auto &info = bot->botInfo) {
|
||||
if (!info->startGroupToken.isEmpty()) {
|
||||
auto request = MTPmessages_StartBot(
|
||||
bot->inputUser,
|
||||
chat->input,
|
||||
MTP_long(rand_value<uint64>()),
|
||||
MTP_string(info->startGroupToken));
|
||||
auto done = App::main()->rpcDone(
|
||||
&MainWidget::sentUpdatesReceived);
|
||||
auto fail = App::main()->rpcFail(
|
||||
&MainWidget::addParticipantFail,
|
||||
{ bot, chat });
|
||||
MTP::send(request, done, fail);
|
||||
MTP::send(
|
||||
MTPmessages_StartBot(
|
||||
bot->inputUser,
|
||||
chat->input,
|
||||
MTP_long(rand_value<uint64>()),
|
||||
MTP_string(info->startGroupToken)),
|
||||
App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
|
||||
App::main()->rpcFail(
|
||||
&MainWidget::addParticipantFail,
|
||||
{ bot, chat }));
|
||||
} else {
|
||||
App::main()->addParticipants(
|
||||
chat,
|
||||
|
||||
@@ -264,17 +264,17 @@ object_ptr<Ui::RpWidget> Controller::createPhotoAndTitleEdit() {
|
||||
auto titleEdit = Ui::AttachParentChild(
|
||||
container,
|
||||
createTitleEdit());
|
||||
photoWrap->heightValue()
|
||||
| rpl::start_with_next([container](int height) {
|
||||
container->resize(container->width(), height);
|
||||
}, photoWrap->lifetime());
|
||||
container->widthValue()
|
||||
| rpl::start_with_next([titleEdit](int width) {
|
||||
auto left = st::editPeerPhotoMargins.left()
|
||||
+ st::defaultUserpicButton.size.width();
|
||||
titleEdit->resizeToWidth(width - left);
|
||||
titleEdit->moveToLeft(left, 0, width);
|
||||
}, titleEdit->lifetime());
|
||||
photoWrap->heightValue(
|
||||
) | rpl::start_with_next([container](int height) {
|
||||
container->resize(container->width(), height);
|
||||
}, photoWrap->lifetime());
|
||||
container->widthValue(
|
||||
) | rpl::start_with_next([titleEdit](int width) {
|
||||
auto left = st::editPeerPhotoMargins.left()
|
||||
+ st::defaultUserpicButton.size.width();
|
||||
titleEdit->resizeToWidth(width - left);
|
||||
titleEdit->moveToLeft(left, 0, width);
|
||||
}, titleEdit->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -439,16 +439,16 @@ object_ptr<Ui::RpWidget> Controller::createUsernameEdit() {
|
||||
base::lambda<QString()>(),
|
||||
channel->username,
|
||||
true));
|
||||
_controls.username->heightValue()
|
||||
| rpl::start_with_next([placeholder](int height) {
|
||||
placeholder->resize(placeholder->width(), height);
|
||||
}, placeholder->lifetime());
|
||||
placeholder->widthValue()
|
||||
| rpl::start_with_next([this](int width) {
|
||||
_controls.username->resize(
|
||||
width,
|
||||
_controls.username->height());
|
||||
}, placeholder->lifetime());
|
||||
_controls.username->heightValue(
|
||||
) | rpl::start_with_next([placeholder](int height) {
|
||||
placeholder->resize(placeholder->width(), height);
|
||||
}, placeholder->lifetime());
|
||||
placeholder->widthValue(
|
||||
) | rpl::start_with_next([this](int width) {
|
||||
_controls.username->resize(
|
||||
width,
|
||||
_controls.username->height());
|
||||
}, placeholder->lifetime());
|
||||
_controls.username->move(placeholder->pos());
|
||||
|
||||
QObject::connect(
|
||||
@@ -624,12 +624,12 @@ void Controller::showUsernameResult(
|
||||
*st);
|
||||
auto label = _controls.usernameResult.get();
|
||||
label->show();
|
||||
label->widthValue()
|
||||
| rpl::start_with_next([label] {
|
||||
label->moveToRight(
|
||||
st::editPeerUsernamePosition.x(),
|
||||
st::editPeerUsernamePosition.y());
|
||||
}, label->lifetime());
|
||||
label->widthValue(
|
||||
) | rpl::start_with_next([label] {
|
||||
label->moveToRight(
|
||||
st::editPeerUsernamePosition.x(),
|
||||
st::editPeerUsernamePosition.y());
|
||||
}, label->lifetime());
|
||||
}
|
||||
_usernameResultTexts.fire(std::move(text));
|
||||
}
|
||||
@@ -729,10 +729,10 @@ object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() {
|
||||
|
||||
Notify::PeerUpdateValue(
|
||||
_peer,
|
||||
Notify::PeerUpdate::Flag::InviteLinkChanged)
|
||||
| rpl::start_with_next([this] {
|
||||
refreshEditInviteLink();
|
||||
}, _controls.editInviteLinkWrap->lifetime());
|
||||
Notify::PeerUpdate::Flag::InviteLinkChanged
|
||||
) | rpl::start_with_next([this] {
|
||||
refreshEditInviteLink();
|
||||
}, _controls.editInviteLinkWrap->lifetime());
|
||||
|
||||
return std::move(result);
|
||||
}
|
||||
@@ -794,10 +794,10 @@ object_ptr<Ui::RpWidget> Controller::createInviteLinkCreate() {
|
||||
|
||||
Notify::PeerUpdateValue(
|
||||
_peer,
|
||||
Notify::PeerUpdate::Flag::InviteLinkChanged)
|
||||
| rpl::start_with_next([this] {
|
||||
refreshCreateInviteLink();
|
||||
}, _controls.createInviteLinkWrap->lifetime());
|
||||
Notify::PeerUpdate::Flag::InviteLinkChanged
|
||||
) | rpl::start_with_next([this] {
|
||||
refreshCreateInviteLink();
|
||||
}, _controls.createInviteLinkWrap->lifetime());
|
||||
|
||||
return std::move(result);
|
||||
}
|
||||
@@ -814,7 +814,8 @@ object_ptr<Ui::RpWidget> Controller::createHistoryVisibilityEdit() {
|
||||
auto channel = _peer->asChannel();
|
||||
if (!channel
|
||||
|| !channel->canEditPreHistoryHidden()
|
||||
|| !channel->isMegagroup()) {
|
||||
|| !channel->isMegagroup()
|
||||
|| (channel->isPublic() && !channel->canEditUsername())) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
@@ -1436,15 +1437,15 @@ EditPeerInfoBox::EditPeerInfoBox(
|
||||
|
||||
void EditPeerInfoBox::prepare() {
|
||||
auto controller = std::make_unique<Controller>(this, _peer);
|
||||
_focusRequests.events()
|
||||
| rpl::start_with_next(
|
||||
[c = controller.get()] { c->setFocus(); },
|
||||
lifetime());
|
||||
_focusRequests.events(
|
||||
) | rpl::start_with_next(
|
||||
[c = controller.get()] { c->setFocus(); },
|
||||
lifetime());
|
||||
auto content = controller->createContent();
|
||||
content->heightValue()
|
||||
| rpl::start_with_next([this](int height) {
|
||||
setDimensions(st::boxWideWidth, height);
|
||||
}, content->lifetime());
|
||||
content->heightValue(
|
||||
) | rpl::start_with_next([this](int height) {
|
||||
setDimensions(st::boxWideWidth, height);
|
||||
}, content->lifetime());
|
||||
setInnerWidget(object_ptr<Ui::IgnoreMargins>(
|
||||
this,
|
||||
std::move(content)));
|
||||
|
||||
@@ -85,13 +85,15 @@ void AddButtonWithCount(
|
||||
std::move(count),
|
||||
st::managePeerButtonLabel);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
rpl::combine(button->widthValue(), label->widthValue())
|
||||
| rpl::start_with_next([label](int outerWidth, int width) {
|
||||
label->moveToRight(
|
||||
st::managePeerButtonLabelPosition.x(),
|
||||
st::managePeerButtonLabelPosition.y(),
|
||||
outerWidth);
|
||||
}, label->lifetime());
|
||||
rpl::combine(
|
||||
button->widthValue(),
|
||||
label->widthValue()
|
||||
) | rpl::start_with_next([label](int outerWidth, int width) {
|
||||
label->moveToRight(
|
||||
st::managePeerButtonLabelPosition.x(),
|
||||
st::managePeerButtonLabelPosition.y(),
|
||||
outerWidth);
|
||||
}, label->lifetime());
|
||||
}
|
||||
|
||||
bool HasRecentActions(not_null<ChannelData*> channel) {
|
||||
@@ -102,7 +104,16 @@ void ShowRecentActions(
|
||||
not_null<Window::Controller*> controller,
|
||||
not_null<ChannelData*> channel) {
|
||||
controller->showSection(AdminLog::SectionMemento(channel));
|
||||
}
|
||||
|
||||
bool HasEditInfoBox(not_null<ChannelData*> channel) {
|
||||
if (channel->canEditInformation()) {
|
||||
return true;
|
||||
} else if (!channel->isPublic() && channel->canAddMembers()) {
|
||||
// Edit invite link.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FillManageBox(
|
||||
@@ -112,7 +123,7 @@ void FillManageBox(
|
||||
using Profile::ParticipantsBoxController;
|
||||
|
||||
auto isGroup = channel->isMegagroup();
|
||||
if (channel->canEditInformation()) {
|
||||
if (HasEditInfoBox(channel)) {
|
||||
AddButton(
|
||||
content,
|
||||
Lang::Viewer(isGroup
|
||||
@@ -157,18 +168,20 @@ void FillManageBox(
|
||||
st::infoIconAdministrators);
|
||||
}
|
||||
if (channel->canViewBanned()) {
|
||||
AddButtonWithCount(
|
||||
content,
|
||||
Lang::Viewer(lng_manage_peer_restricted_users),
|
||||
Info::Profile::RestrictedCountValue(channel)
|
||||
| ToPositiveNumberString(),
|
||||
[=] {
|
||||
ParticipantsBoxController::Start(
|
||||
controller,
|
||||
channel,
|
||||
ParticipantsBoxController::Role::Restricted);
|
||||
},
|
||||
st::infoIconRestrictedUsers);
|
||||
if (channel->isMegagroup()) {
|
||||
AddButtonWithCount(
|
||||
content,
|
||||
Lang::Viewer(lng_manage_peer_restricted_users),
|
||||
Info::Profile::RestrictedCountValue(channel)
|
||||
| ToPositiveNumberString(),
|
||||
[=] {
|
||||
ParticipantsBoxController::Start(
|
||||
controller,
|
||||
channel,
|
||||
ParticipantsBoxController::Role::Restricted);
|
||||
},
|
||||
st::infoIconRestrictedUsers);
|
||||
}
|
||||
AddButtonWithCount(
|
||||
content,
|
||||
Lang::Viewer(lng_manage_peer_banned_users),
|
||||
@@ -219,12 +232,12 @@ void ManagePeerBox::prepare() {
|
||||
void ManagePeerBox::setupContent() {
|
||||
auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
FillManageBox(controller(), _channel, content);
|
||||
widthValue()
|
||||
| rpl::start_with_next([=](int width) {
|
||||
content->resizeToWidth(width);
|
||||
}, content->lifetime());
|
||||
content->heightValue()
|
||||
| rpl::start_with_next([=](int height) {
|
||||
setDimensions(st::boxWidth, height);
|
||||
}, content->lifetime());
|
||||
widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
content->resizeToWidth(width);
|
||||
}, content->lifetime());
|
||||
content->heightValue(
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
setDimensions(st::boxWidth, height);
|
||||
}, content->lifetime());
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,25 +20,40 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <rpl/variable.h>
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
|
||||
namespace Ui {
|
||||
class Checkbox;
|
||||
template <typename Enum>
|
||||
class Radioenum;
|
||||
template <typename Enum>
|
||||
class RadioenumGroup;
|
||||
class RoundButton;
|
||||
class InputArea;
|
||||
class EmptyUserpic;
|
||||
struct GroupMediaLayout;
|
||||
} // namespace Ui
|
||||
|
||||
enum class SendFilesWay {
|
||||
Album,
|
||||
Photos,
|
||||
Files,
|
||||
};
|
||||
|
||||
class SendFilesBox : public BoxContent {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SendFilesBox(QWidget*, QImage image, CompressConfirm compressed);
|
||||
SendFilesBox(QWidget*, const QStringList &files, CompressConfirm compressed);
|
||||
SendFilesBox(QWidget*, const QString &phone, const QString &firstname, const QString &lastname);
|
||||
SendFilesBox(
|
||||
QWidget*,
|
||||
Storage::PreparedList &&list,
|
||||
CompressConfirm compressed);
|
||||
|
||||
void setConfirmedCallback(base::lambda<void(const QStringList &files, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, bool compressed, const QString &caption, bool ctrlShiftEnter)> callback) {
|
||||
void setConfirmedCallback(
|
||||
base::lambda<void(
|
||||
Storage::PreparedList &&list,
|
||||
SendFilesWay way,
|
||||
const QString &caption,
|
||||
bool ctrlShiftEnter)> callback) {
|
||||
_confirmedCallback = std::move(callback);
|
||||
}
|
||||
void setCancelledCallback(base::lambda<void()> callback) {
|
||||
@@ -55,116 +70,64 @@ protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private slots:
|
||||
void onCompressedChange();
|
||||
void onSend(bool ctrlShiftEnter = false);
|
||||
void onCaptionResized();
|
||||
void onClose() {
|
||||
closeBox();
|
||||
}
|
||||
|
||||
private:
|
||||
void prepareSingleFileLayout();
|
||||
void prepareDocumentLayout();
|
||||
void tryToReadSingleFile();
|
||||
void prepareGifPreview();
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
class AlbumPreview;
|
||||
|
||||
void updateTitleText();
|
||||
void initSendWay();
|
||||
void initPreview(rpl::producer<int> desiredPreviewHeight);
|
||||
|
||||
void setupControls();
|
||||
void setupSendWayControls();
|
||||
void setupCaption();
|
||||
void setupShadows(
|
||||
not_null<Ui::ScrollArea*> wrap,
|
||||
not_null<AlbumPreview*> content);
|
||||
|
||||
void refreshAlbumMediaCount();
|
||||
void preparePreview();
|
||||
void prepareSingleFilePreview();
|
||||
void prepareAlbumPreview();
|
||||
void applyAlbumOrder();
|
||||
|
||||
void send(bool ctrlShiftEnter = false);
|
||||
void captionResized();
|
||||
|
||||
void setupTitleText();
|
||||
void updateBoxSize();
|
||||
void updateControlsGeometry();
|
||||
base::lambda<QString()> getSendButtonText() const;
|
||||
|
||||
bool canAddFiles(not_null<const QMimeData*> data) const;
|
||||
bool addFiles(not_null<const QMimeData*> data);
|
||||
|
||||
QString _titleText;
|
||||
QStringList _files;
|
||||
QImage _image;
|
||||
std::unique_ptr<FileLoadTask::MediaInformation> _information;
|
||||
int _titleHeight = 0;
|
||||
|
||||
Storage::PreparedList _list;
|
||||
|
||||
CompressConfirm _compressConfirmInitial = CompressConfirm::None;
|
||||
CompressConfirm _compressConfirm = CompressConfirm::None;
|
||||
bool _animated = false;
|
||||
|
||||
QPixmap _preview;
|
||||
int _previewLeft = 0;
|
||||
int _previewWidth = 0;
|
||||
int _previewHeight = 0;
|
||||
Media::Clip::ReaderPointer _gifPreview;
|
||||
|
||||
QPixmap _fileThumb;
|
||||
Text _nameText;
|
||||
bool _fileIsAudio = false;
|
||||
bool _fileIsImage = false;
|
||||
QString _statusText;
|
||||
int _statusWidth = 0;
|
||||
|
||||
QString _contactPhone;
|
||||
QString _contactFirstName;
|
||||
QString _contactLastName;
|
||||
std::unique_ptr<Ui::EmptyUserpic> _contactPhotoEmpty;
|
||||
|
||||
base::lambda<void(const QStringList &files, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, bool compressed, const QString &caption, bool ctrlShiftEnter)> _confirmedCallback;
|
||||
base::lambda<void(
|
||||
Storage::PreparedList &&list,
|
||||
SendFilesWay way,
|
||||
const QString &caption,
|
||||
bool ctrlShiftEnter)> _confirmedCallback;
|
||||
base::lambda<void()> _cancelledCallback;
|
||||
bool _confirmed = false;
|
||||
|
||||
object_ptr<Ui::InputArea> _caption = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _compressed = { nullptr };
|
||||
object_ptr<Ui::Radioenum<SendFilesWay>> _sendAlbum = { nullptr };
|
||||
object_ptr<Ui::Radioenum<SendFilesWay>> _sendPhotos = { nullptr };
|
||||
object_ptr<Ui::Radioenum<SendFilesWay>> _sendFiles = { nullptr };
|
||||
std::shared_ptr<Ui::RadioenumGroup<SendFilesWay>> _sendWay;
|
||||
|
||||
rpl::variable<int> _footerHeight = 0;
|
||||
|
||||
QWidget *_preview = nullptr;
|
||||
AlbumPreview *_albumPreview = nullptr;
|
||||
int _albumVideosCount = 0;
|
||||
int _albumPhotosCount = 0;
|
||||
|
||||
QPointer<Ui::RoundButton> _send;
|
||||
|
||||
};
|
||||
|
||||
class EditCaptionBox : public BoxContent, public RPCSender {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
EditCaptionBox(QWidget*, HistoryMedia *media, FullMsgId msgId);
|
||||
|
||||
public slots:
|
||||
void onCaptionResized();
|
||||
void onSave(bool ctrlShiftEnter = false);
|
||||
void onClose() {
|
||||
closeBox();
|
||||
}
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
void updateBoxSize();
|
||||
void prepareGifPreview(DocumentData *document);
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
|
||||
void saveDone(const MTPUpdates &updates);
|
||||
bool saveFail(const RPCError &error);
|
||||
|
||||
int errorTopSkip() const;
|
||||
|
||||
FullMsgId _msgId;
|
||||
bool _animated = false;
|
||||
bool _photo = false;
|
||||
bool _doc = false;
|
||||
|
||||
QPixmap _thumb;
|
||||
Media::Clip::ReaderPointer _gifPreview;
|
||||
|
||||
object_ptr<Ui::InputArea> _field = { nullptr };
|
||||
|
||||
int _thumbx = 0;
|
||||
int _thumby = 0;
|
||||
int _thumbw = 0;
|
||||
int _thumbh = 0;
|
||||
Text _name;
|
||||
QString _status;
|
||||
int _statusw = 0;
|
||||
bool _isAudio = false;
|
||||
bool _isImage = false;
|
||||
|
||||
bool _previewCancelled = false;
|
||||
mtpRequestId _saveRequestId = 0;
|
||||
|
||||
QString _error;
|
||||
|
||||
};
|
||||
|
||||
@@ -33,10 +33,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "apiwrap.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_message.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_message.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "auth_session.h"
|
||||
@@ -52,7 +53,7 @@ ShareBox::ShareBox(QWidget*, CopyCallback &©Callback, SubmitCallback &&submi
|
||||
|
||||
void ShareBox::prepare() {
|
||||
_select->resizeToWidth(st::boxWideWidth);
|
||||
myEnsureResized(_select);
|
||||
Ui::SendPendingMoveResizeEvents(_select);
|
||||
|
||||
setTitle(langFactory(lng_share_title));
|
||||
|
||||
@@ -377,7 +378,7 @@ void ShareBox::Inner::updateChatName(
|
||||
not_null<Chat*> chat,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto text = peer->isSelf() ? lang(lng_saved_messages) : peer->name;
|
||||
chat->name.setText(st::shareNameStyle, text, _textNameOptions);
|
||||
chat->name.setText(st::shareNameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
void ShareBox::Inner::repaintChatAtIndex(int index) {
|
||||
|
||||
@@ -50,21 +50,21 @@ void StickerSetBox::prepare() {
|
||||
setTitle(langFactory(lng_contacts_loading));
|
||||
|
||||
_inner = setInnerWidget(object_ptr<Inner>(this, _set), st::stickersScroll);
|
||||
Auth().data().stickersUpdated()
|
||||
| rpl::start_with_next(
|
||||
[this] { updateButtons(); },
|
||||
lifetime());
|
||||
Auth().data().stickersUpdated(
|
||||
) | rpl::start_with_next(
|
||||
[this] { updateButtons(); },
|
||||
lifetime());
|
||||
|
||||
setDimensions(st::boxWideWidth, st::stickersMaxHeight);
|
||||
|
||||
onUpdateButtons();
|
||||
|
||||
connect(_inner, SIGNAL(updateButtons()), this, SLOT(onUpdateButtons()));
|
||||
_inner->setInstalled()
|
||||
| rpl::start_with_next([this](auto &&setId) {
|
||||
Auth().api().stickerSetInstalled(setId);
|
||||
this->closeBox();
|
||||
}, lifetime());
|
||||
_inner->setInstalled(
|
||||
) | rpl::start_with_next([this](auto &&setId) {
|
||||
Auth().api().stickerSetInstalled(setId);
|
||||
this->closeBox();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void StickerSetBox::onAddStickers() {
|
||||
|
||||
@@ -90,11 +90,11 @@ StickersBox::CounterWidget::CounterWidget(QWidget *parent)
|
||||
_st.padding = st::stickersFeaturedBadgePadding;
|
||||
_st.font = st::stickersFeaturedBadgeFont;
|
||||
|
||||
Auth().data().featuredStickerSetsUnreadCountValue()
|
||||
| rpl::start_with_next([this](int count) {
|
||||
setCounter(count);
|
||||
update();
|
||||
}, lifetime());
|
||||
Auth().data().featuredStickerSetsUnreadCountValue(
|
||||
) | rpl::start_with_next([this](int count) {
|
||||
setCounter(count);
|
||||
update();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void StickersBox::CounterWidget::setCounter(int counter) {
|
||||
@@ -242,10 +242,10 @@ void StickersBox::prepare() {
|
||||
preloadArchivedSets();
|
||||
}
|
||||
setNoContentMargin(true);
|
||||
_tabs->sectionActivated()
|
||||
| rpl::start_with_next(
|
||||
[this] { switchTab(); },
|
||||
lifetime());
|
||||
_tabs->sectionActivated(
|
||||
) | rpl::start_with_next(
|
||||
[this] { switchTab(); },
|
||||
lifetime());
|
||||
refreshTabs();
|
||||
}
|
||||
if (_installed.widget() && _section != Section::Installed) _installed.widget()->hide();
|
||||
@@ -277,10 +277,10 @@ void StickersBox::prepare() {
|
||||
setInnerWidget(_tab->takeWidget(), getTopSkip());
|
||||
setDimensions(st::boxWideWidth, st::boxMaxListHeight);
|
||||
|
||||
Auth().data().stickersUpdated()
|
||||
| rpl::start_with_next(
|
||||
[this] { handleStickersUpdated(); },
|
||||
lifetime());
|
||||
Auth().data().stickersUpdated(
|
||||
) | rpl::start_with_next(
|
||||
[this] { handleStickersUpdated(); },
|
||||
lifetime());
|
||||
Auth().api().updateStickers();
|
||||
|
||||
if (_installed.widget()) {
|
||||
@@ -1551,13 +1551,13 @@ int StickersBox::Inner::fillSetCount(const Stickers::Set &set) const {
|
||||
auto customIt = Auth().data().stickerSets().constFind(Stickers::CustomSetId);
|
||||
if (customIt != Auth().data().stickerSets().cend()) {
|
||||
added = customIt->stickers.size();
|
||||
for_const (auto &sticker, cGetRecentStickers()) {
|
||||
for_const (auto &sticker, Stickers::GetRecentPack()) {
|
||||
if (customIt->stickers.indexOf(sticker.first) < 0) {
|
||||
++added;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
added = cGetRecentStickers().size();
|
||||
added = Stickers::GetRecentPack().size();
|
||||
}
|
||||
}
|
||||
return result + added;
|
||||
|
||||
@@ -214,19 +214,19 @@ void BoxController::Row::stopLastActionRipple() {
|
||||
}
|
||||
|
||||
void BoxController::prepare() {
|
||||
Auth().data().itemRemoved()
|
||||
| rpl::start_with_next([this](auto item) {
|
||||
if (auto row = rowForItem(item)) {
|
||||
row->itemRemoved(item);
|
||||
if (!row->hasItems()) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
if (!delegate()->peerListFullRowsCount()) {
|
||||
refreshAbout();
|
||||
}
|
||||
Auth().data().itemRemoved(
|
||||
) | rpl::start_with_next([this](auto item) {
|
||||
if (auto row = rowForItem(item)) {
|
||||
row->itemRemoved(item);
|
||||
if (!row->hasItems()) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
if (!delegate()->peerListFullRowsCount()) {
|
||||
refreshAbout();
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}, lifetime());
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}, lifetime());
|
||||
subscribe(Current().newServiceMessage(), [this](const FullMsgId &msgId) {
|
||||
if (auto item = App::histItemById(msgId)) {
|
||||
insertRow(item, InsertWay::Prepend);
|
||||
|
||||
@@ -37,7 +37,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "apiwrap.h"
|
||||
#include "observer_peer.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "window/main_window.h"
|
||||
|
||||
namespace Calls {
|
||||
@@ -365,10 +364,12 @@ void Panel::initLayout() {
|
||||
|
||||
initGeometry();
|
||||
|
||||
Notify::PeerUpdateValue(_user, Notify::PeerUpdate::Flag::PhotoChanged)
|
||||
| rpl::start_with_next(
|
||||
[this] { processUserPhoto(); },
|
||||
lifetime());
|
||||
Notify::PeerUpdateValue(
|
||||
_user,
|
||||
Notify::PeerUpdate::Flag::PhotoChanged
|
||||
) | rpl::start_with_next(
|
||||
[this] { processUserPhoto(); },
|
||||
lifetime());
|
||||
subscribe(Auth().downloaderTaskFinished(), [this] {
|
||||
refreshUserPhoto();
|
||||
});
|
||||
@@ -386,10 +387,15 @@ void Panel::toggleOpacityAnimation(bool visible) {
|
||||
if (_useTransparency) {
|
||||
if (_animationCache.isNull()) {
|
||||
showControls();
|
||||
_animationCache = myGrab(this);
|
||||
_animationCache = Ui::GrabWidget(this);
|
||||
hideChildren();
|
||||
}
|
||||
_opacityAnimation.start([this] { update(); }, _visible ? 0. : 1., _visible ? 1. : 0., st::callPanelDuration, _visible ? anim::easeOutCirc : anim::easeInCirc);
|
||||
_opacityAnimation.start(
|
||||
[this] { update(); },
|
||||
_visible ? 0. : 1.,
|
||||
_visible ? 1. : 0.,
|
||||
st::callPanelDuration,
|
||||
_visible ? anim::easeOutCirc : anim::easeInCirc);
|
||||
}
|
||||
if (isHidden() && _visible) {
|
||||
show();
|
||||
@@ -418,10 +424,8 @@ void Panel::showControls() {
|
||||
|
||||
void Panel::destroyDelayed() {
|
||||
hide();
|
||||
base::TaskQueue::Main().Put([weak = QPointer<Panel>(this)] {
|
||||
if (weak) {
|
||||
delete weak.data();
|
||||
}
|
||||
crl::on_main(this, [=] {
|
||||
delete this;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -490,7 +494,7 @@ void Panel::createUserpicCache(ImagePtr image) {
|
||||
_user->name
|
||||
).paintSquare(p, 0, 0, st::callWidth, st::callWidth);
|
||||
}
|
||||
Images::prepareRound(filled, ImageRoundRadius::Large, ImageRoundCorner::TopLeft | ImageRoundCorner::TopRight);
|
||||
Images::prepareRound(filled, ImageRoundRadius::Large, RectPart::TopLeft | RectPart::TopRight);
|
||||
_userPhoto = App::pixmapFromImageInPlace(std::move(filled));
|
||||
}
|
||||
refreshCacheImageUserPhoto();
|
||||
|
||||
@@ -20,9 +20,91 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "chat_helpers/bot_keyboard.h"
|
||||
|
||||
#include "history/history_item_components.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_history.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class Style : public ReplyKeyboard::Style {
|
||||
public:
|
||||
Style(
|
||||
not_null<BotKeyboard*> parent,
|
||||
const style::BotKeyboardButton &st);
|
||||
|
||||
int buttonRadius() const override;
|
||||
|
||||
void startPaint(Painter &p) const override;
|
||||
const style::TextStyle &textStyle() const override;
|
||||
void repaint(not_null<const HistoryItem*> item) const override;
|
||||
|
||||
protected:
|
||||
void paintButtonBg(
|
||||
Painter &p,
|
||||
const QRect &rect,
|
||||
float64 howMuchOver) const override;
|
||||
void paintButtonIcon(
|
||||
Painter &p,
|
||||
const QRect &rect,
|
||||
int outerWidth,
|
||||
HistoryMessageMarkupButton::Type type) const override;
|
||||
void paintButtonLoading(Painter &p, const QRect &rect) const override;
|
||||
int minButtonWidth(HistoryMessageMarkupButton::Type type) const override;
|
||||
|
||||
private:
|
||||
not_null<BotKeyboard*> _parent;
|
||||
|
||||
};
|
||||
|
||||
Style::Style(
|
||||
not_null<BotKeyboard*> parent,
|
||||
const style::BotKeyboardButton &st)
|
||||
: ReplyKeyboard::Style(st), _parent(parent) {
|
||||
}
|
||||
|
||||
void Style::startPaint(Painter &p) const {
|
||||
p.setPen(st::botKbColor);
|
||||
p.setFont(st::botKbStyle.font);
|
||||
}
|
||||
|
||||
const style::TextStyle &Style::textStyle() const {
|
||||
return st::botKbStyle;
|
||||
}
|
||||
|
||||
void Style::repaint(not_null<const HistoryItem*> item) const {
|
||||
_parent->update();
|
||||
}
|
||||
|
||||
int Style::buttonRadius() const {
|
||||
return st::buttonRadius;
|
||||
}
|
||||
|
||||
void Style::paintButtonBg(
|
||||
Painter &p,
|
||||
const QRect &rect,
|
||||
float64 howMuchOver) const {
|
||||
App::roundRect(p, rect, st::botKbBg, BotKeyboardCorners);
|
||||
}
|
||||
|
||||
void Style::paintButtonIcon(
|
||||
Painter &p,
|
||||
const QRect &rect,
|
||||
int outerWidth,
|
||||
HistoryMessageMarkupButton::Type type) const {
|
||||
// Buttons with icons should not appear here.
|
||||
}
|
||||
|
||||
void Style::paintButtonLoading(Painter &p, const QRect &rect) const {
|
||||
// Buttons with loading progress should not appear here.
|
||||
}
|
||||
|
||||
int Style::minButtonWidth(HistoryMessageMarkupButton::Type type) const {
|
||||
int result = 2 * buttonPadding();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BotKeyboard::BotKeyboard(QWidget *parent) : TWidget(parent)
|
||||
, _st(&st::botKbButton) {
|
||||
setGeometry(0, 0, _st->margin, st::botKbScroll.deltat);
|
||||
@@ -43,40 +125,6 @@ void BotKeyboard::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
void BotKeyboard::Style::startPaint(Painter &p) const {
|
||||
p.setPen(st::botKbColor);
|
||||
p.setFont(st::botKbStyle.font);
|
||||
}
|
||||
|
||||
const style::TextStyle &BotKeyboard::Style::textStyle() const {
|
||||
return st::botKbStyle;
|
||||
}
|
||||
|
||||
void BotKeyboard::Style::repaint(not_null<const HistoryItem*> item) const {
|
||||
_parent->update();
|
||||
}
|
||||
|
||||
int BotKeyboard::Style::buttonRadius() const {
|
||||
return st::buttonRadius;
|
||||
}
|
||||
|
||||
void BotKeyboard::Style::paintButtonBg(Painter &p, const QRect &rect, float64 howMuchOver) const {
|
||||
App::roundRect(p, rect, st::botKbBg, BotKeyboardCorners);
|
||||
}
|
||||
|
||||
void BotKeyboard::Style::paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageReplyMarkup::Button::Type type) const {
|
||||
// Buttons with icons should not appear here.
|
||||
}
|
||||
|
||||
void BotKeyboard::Style::paintButtonLoading(Painter &p, const QRect &rect) const {
|
||||
// Buttons with loading progress should not appear here.
|
||||
}
|
||||
|
||||
int BotKeyboard::Style::minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const {
|
||||
int result = 2 * buttonPadding();
|
||||
return result;
|
||||
}
|
||||
|
||||
void BotKeyboard::mousePressEvent(QMouseEvent *e) {
|
||||
_lastMousePos = e->globalPos();
|
||||
updateSelected();
|
||||
@@ -108,16 +156,18 @@ void BotKeyboard::leaveEventHook(QEvent *e) {
|
||||
}
|
||||
|
||||
bool BotKeyboard::moderateKeyActivate(int key) {
|
||||
if (auto item = App::histItemById(_wasForMsgId)) {
|
||||
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (const auto item = App::histItemById(_wasForMsgId)) {
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (key >= Qt::Key_1 && key <= Qt::Key_9) {
|
||||
int index = (key - Qt::Key_1);
|
||||
if (!markup->rows.isEmpty() && index >= 0 && index < markup->rows.front().size()) {
|
||||
const auto index = int(key - Qt::Key_1);
|
||||
if (!markup->rows.empty()
|
||||
&& index >= 0
|
||||
&& index < int(markup->rows.front().size())) {
|
||||
App::activateBotCommand(item, 0, index);
|
||||
return true;
|
||||
}
|
||||
} else if (key == Qt::Key_Q) {
|
||||
if (auto user = item->history()->peer->asUser()) {
|
||||
if (const auto user = item->history()->peer->asUser()) {
|
||||
if (user->botInfo && item->from() == user) {
|
||||
App::sendBotCommand(user, user, qsl("/translate"));
|
||||
return true;
|
||||
@@ -163,8 +213,10 @@ bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
|
||||
|
||||
_impl = nullptr;
|
||||
if (auto markup = to->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (!markup->rows.isEmpty()) {
|
||||
_impl.reset(new ReplyKeyboard(to, std::make_unique<Style>(this, *_st)));
|
||||
if (!markup->rows.empty()) {
|
||||
_impl = std::make_unique<ReplyKeyboard>(
|
||||
to,
|
||||
std::make_unique<Style>(this, *_st));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,3 +298,5 @@ void BotKeyboard::updateSelected() {
|
||||
setCursor(link ? style::cur_pointer : style::cur_default);
|
||||
}
|
||||
}
|
||||
|
||||
BotKeyboard::~BotKeyboard() = default;
|
||||
|
||||
@@ -22,7 +22,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
#include "ui/widgets/tooltip.h"
|
||||
|
||||
class BotKeyboard : public TWidget, public Ui::AbstractTooltipShower, public ClickHandlerHost {
|
||||
class ReplyKeyboard;
|
||||
|
||||
class BotKeyboard
|
||||
: public TWidget
|
||||
, public Ui::AbstractTooltipShower
|
||||
, public ClickHandlerHost {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@@ -57,6 +62,8 @@ public:
|
||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
||||
|
||||
~BotKeyboard();
|
||||
|
||||
protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
|
||||
@@ -83,27 +90,6 @@ private:
|
||||
QPoint _lastMousePos;
|
||||
std::unique_ptr<ReplyKeyboard> _impl;
|
||||
|
||||
class Style : public ReplyKeyboard::Style {
|
||||
public:
|
||||
Style(BotKeyboard *parent, const style::BotKeyboardButton &st) : ReplyKeyboard::Style(st), _parent(parent) {
|
||||
}
|
||||
|
||||
int buttonRadius() const override;
|
||||
|
||||
void startPaint(Painter &p) const override;
|
||||
const style::TextStyle &textStyle() const override;
|
||||
void repaint(not_null<const HistoryItem*> item) const override;
|
||||
|
||||
protected:
|
||||
void paintButtonBg(Painter &p, const QRect &rect, float64 howMuchOver) const override;
|
||||
void paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageReplyMarkup::Button::Type type) const override;
|
||||
void paintButtonLoading(Painter &p, const QRect &rect) const override;
|
||||
int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const override;
|
||||
|
||||
private:
|
||||
BotKeyboard *_parent;
|
||||
|
||||
};
|
||||
const style::BotKeyboardButton *_st = nullptr;
|
||||
|
||||
};
|
||||
|
||||
@@ -250,7 +250,7 @@ void EmojiColorPicker::hideFast() {
|
||||
|
||||
void EmojiColorPicker::hideAnimated() {
|
||||
if (_cache.isNull()) {
|
||||
_cache = myGrab(this);
|
||||
_cache = Ui::GrabWidget(this);
|
||||
clearSelection();
|
||||
}
|
||||
_hiding = true;
|
||||
@@ -265,7 +265,7 @@ void EmojiColorPicker::showAnimated() {
|
||||
}
|
||||
_hiding = false;
|
||||
if (_cache.isNull()) {
|
||||
_cache = myGrab(this);
|
||||
_cache = Ui::GrabWidget(this);
|
||||
clearSelection();
|
||||
}
|
||||
show();
|
||||
|
||||
@@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "chat_helpers/field_autocomplete.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
#include "storage/localstorage.h"
|
||||
@@ -191,7 +192,10 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
}
|
||||
}
|
||||
if (_chat) {
|
||||
QMultiMap<int32, UserData*> ordered;
|
||||
auto ordered = QMultiMap<TimeId, not_null<UserData*>>();
|
||||
const auto byOnline = [&](not_null<UserData*> user) {
|
||||
return Data::SortByOnlineValue(user, now);
|
||||
};
|
||||
mrows.reserve(mrows.size() + (_chat->participants.empty() ? _chat->lastAuthors.size() : _chat->participants.size()));
|
||||
if (_chat->noParticipantInfo()) {
|
||||
Auth().api().requestFullPeer(_chat);
|
||||
@@ -200,7 +204,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
if (user->isInaccessible()) continue;
|
||||
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
|
||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||
ordered.insertMulti(App::onlineForSort(user, now), user);
|
||||
ordered.insertMulti(byOnline(user), user);
|
||||
}
|
||||
}
|
||||
for (const auto user : _chat->lastAuthors) {
|
||||
@@ -209,7 +213,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||
mrows.push_back(user);
|
||||
if (!ordered.isEmpty()) {
|
||||
ordered.remove(App::onlineForSort(user, now), user);
|
||||
ordered.remove(byOnline(user), user);
|
||||
}
|
||||
}
|
||||
if (!ordered.isEmpty()) {
|
||||
@@ -404,7 +408,7 @@ void FieldAutocomplete::hideAnimated() {
|
||||
|
||||
if (_cache.isNull()) {
|
||||
_scroll->show();
|
||||
_cache = myGrab(this);
|
||||
_cache = Ui::GrabWidget(this);
|
||||
}
|
||||
_scroll->hide();
|
||||
_hiding = true;
|
||||
@@ -425,7 +429,7 @@ void FieldAutocomplete::showAnimated() {
|
||||
}
|
||||
if (_cache.isNull()) {
|
||||
_scroll->show();
|
||||
_cache = myGrab(this);
|
||||
_cache = Ui::GrabWidget(this);
|
||||
}
|
||||
_scroll->hide();
|
||||
_hiding = false;
|
||||
|
||||
@@ -148,10 +148,10 @@ GifsListWidget::GifsListWidget(
|
||||
_inlineRequestTimer.setSingleShot(true);
|
||||
connect(&_inlineRequestTimer, &QTimer::timeout, this, [this] { sendInlineRequest(); });
|
||||
|
||||
Auth().data().savedGifsUpdated()
|
||||
| rpl::start_with_next([this] {
|
||||
refreshSavedGifs();
|
||||
}, lifetime());
|
||||
Auth().data().savedGifsUpdated(
|
||||
) | rpl::start_with_next([this] {
|
||||
refreshSavedGifs();
|
||||
}, lifetime());
|
||||
subscribe(Auth().downloaderTaskFinished(), [this] {
|
||||
update();
|
||||
});
|
||||
@@ -335,7 +335,7 @@ void GifsListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dynamic_cast<InlineBots::Layout::SendClickHandler*>(activated.data())) {
|
||||
if (dynamic_cast<InlineBots::Layout::SendClickHandler*>(activated.get())) {
|
||||
int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift;
|
||||
selectInlineResult(row, column);
|
||||
} else {
|
||||
@@ -596,12 +596,13 @@ void GifsListWidget::layoutInlineRow(Row &row, int fullWidth) {
|
||||
row.height = 0;
|
||||
int availw = fullWidth - (st::inlineResultsLeft - st::buttonRadius);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
int index = indices[i];
|
||||
int w = desiredWidth
|
||||
? (row.items[index]->maxWidth() * availw / desiredWidth)
|
||||
: row.items[index]->maxWidth();
|
||||
int actualw = qMax(w, int(st::inlineResultsMinWidth));
|
||||
row.height = qMax(row.height, row.items[index]->resizeGetHeight(actualw));
|
||||
const auto index = indices[i];
|
||||
const auto &item = row.items[index];
|
||||
const auto w = desiredWidth
|
||||
? (item->maxWidth() * availw / desiredWidth)
|
||||
: item->maxWidth();
|
||||
auto actualw = qMax(w, st::inlineResultsMinWidth);
|
||||
row.height = qMax(row.height, item->resizeGetHeight(actualw));
|
||||
if (desiredWidth) {
|
||||
availw -= actualw;
|
||||
desiredWidth -= row.items[index]->maxWidth();
|
||||
|
||||
@@ -347,7 +347,7 @@ void SetsReceived(const QVector<MTPStickerSet> &data, int32 hash) {
|
||||
}
|
||||
}
|
||||
auto writeRecent = false;
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = GetRecentPack();
|
||||
for (auto it = sets.begin(), e = sets.end(); it != e;) {
|
||||
bool installed = (it->flags & MTPDstickerSet::Flag::f_installed);
|
||||
bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured);
|
||||
@@ -449,7 +449,7 @@ void SpecialSetReceived(uint64 setId, const QString &setTitle, const QVector<MTP
|
||||
}
|
||||
|
||||
auto writeRecent = false;
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = GetRecentPack();
|
||||
for (auto i = recent.begin(); i != recent.cend();) {
|
||||
if (it->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
|
||||
i = recent.erase(i);
|
||||
@@ -748,7 +748,7 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data) {
|
||||
}
|
||||
|
||||
auto writeRecent = false;
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = GetRecentPack();
|
||||
for (auto i = recent.begin(); i != recent.cend();) {
|
||||
if (set->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
|
||||
i = recent.erase(i);
|
||||
@@ -816,4 +816,56 @@ QString GetSetTitle(const MTPDstickerSet &s) {
|
||||
return title;
|
||||
}
|
||||
|
||||
RecentStickerPack &GetRecentPack() {
|
||||
if (cRecentStickers().isEmpty() && !cRecentStickersPreload().isEmpty()) {
|
||||
const auto p = cRecentStickersPreload();
|
||||
cSetRecentStickersPreload(RecentStickerPreload());
|
||||
|
||||
auto &recent = cRefRecentStickers();
|
||||
recent.reserve(p.size());
|
||||
for (const auto &preloaded : p) {
|
||||
const auto document = App::document(preloaded.first);
|
||||
if (!document || !document->sticker()) continue;
|
||||
|
||||
recent.push_back(qMakePair(document, preloaded.second));
|
||||
}
|
||||
}
|
||||
return cRefRecentStickers();
|
||||
}
|
||||
|
||||
void IncrementRecentHashtag(RecentHashtagPack &recent, const QString &tag) {
|
||||
auto i = recent.begin(), e = recent.end();
|
||||
for (; i != e; ++i) {
|
||||
if (i->first == tag) {
|
||||
++i->second;
|
||||
if (qAbs(i->second) > 0x4000) {
|
||||
for (auto j = recent.begin(); j != e; ++j) {
|
||||
if (j->second > 1) {
|
||||
j->second /= 2;
|
||||
} else if (j->second > 0) {
|
||||
j->second = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; i != recent.begin(); --i) {
|
||||
if (qAbs((i - 1)->second) > qAbs(i->second)) {
|
||||
break;
|
||||
}
|
||||
qSwap(*i, *(i - 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == e) {
|
||||
while (recent.size() >= 64) recent.pop_back();
|
||||
recent.push_back(qMakePair(tag, 1));
|
||||
for (i = recent.end() - 1; i != recent.begin(); --i) {
|
||||
if ((i - 1)->second > i->second) {
|
||||
break;
|
||||
}
|
||||
qSwap(*i, *(i - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Stickers
|
||||
|
||||
@@ -85,4 +85,8 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data);
|
||||
|
||||
QString GetSetTitle(const MTPDstickerSet &s);
|
||||
|
||||
RecentStickerPack &GetRecentPack();
|
||||
|
||||
void IncrementRecentHashtag(RecentHashtagPack &recent, const QString &tag);
|
||||
|
||||
} // namespace Stickers
|
||||
|
||||
@@ -113,6 +113,23 @@ private:
|
||||
|
||||
};
|
||||
|
||||
StickersListWidget::Set::Set(
|
||||
uint64 id,
|
||||
MTPDstickerSet::Flags flags,
|
||||
const QString &title,
|
||||
int hoversSize,
|
||||
const Stickers::Pack &pack)
|
||||
: id(id)
|
||||
, flags(flags)
|
||||
, title(title)
|
||||
, pack(pack) {
|
||||
}
|
||||
|
||||
StickersListWidget::Set::Set(Set &&other) = default;
|
||||
StickersListWidget::Set &StickersListWidget::Set::operator=(
|
||||
Set &&other) = default;
|
||||
StickersListWidget::Set::~Set() = default;
|
||||
|
||||
StickersListWidget::Footer::Footer(not_null<StickersListWidget*> parent) : InnerFooter(parent)
|
||||
, _pan(parent)
|
||||
, _a_icons(animation(this, &Footer::step_icons)) {
|
||||
@@ -605,10 +622,12 @@ int StickersListWidget::countDesiredHeight(int newWidth) {
|
||||
auto minimalLastHeight = (visibleHeight - st::stickerPanPadding);
|
||||
auto countResult = [this, minimalLastHeight] {
|
||||
if (_section == Section::Featured) {
|
||||
return st::stickerPanPadding + shownSets().size() * featuredRowHeight();
|
||||
return st::stickerPanPadding
|
||||
+ int(shownSets().size()) * featuredRowHeight();
|
||||
} else if (!shownSets().empty()) {
|
||||
auto info = sectionInfo(shownSets().size() - 1);
|
||||
return info.top + qMax(info.rowsBottom - info.top, minimalLastHeight);
|
||||
const auto info = sectionInfo(shownSets().size() - 1);
|
||||
return info.top
|
||||
+ qMax(info.rowsBottom - info.top, minimalLastHeight);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
@@ -679,7 +698,7 @@ void StickersListWidget::paintFeaturedStickers(Painter &p, QRect clip) {
|
||||
|
||||
auto tilly = st::stickerPanPadding;
|
||||
auto ms = getms();
|
||||
for (auto c = 0, l = sets.size(); c != l; ++c) {
|
||||
for (auto c = 0, l = int(sets.size()); c != l; ++c) {
|
||||
auto y = tilly;
|
||||
auto &set = sets[c];
|
||||
tilly = y + featuredRowHeight();
|
||||
@@ -1005,19 +1024,21 @@ QRect StickersListWidget::megagroupSetButtonRectFinal() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
QSharedPointer<Ui::RippleAnimation> StickersListWidget::createButtonRipple(int section) {
|
||||
std::unique_ptr<Ui::RippleAnimation> StickersListWidget::createButtonRipple(int section) {
|
||||
if (_section == Section::Featured) {
|
||||
auto maskSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height);
|
||||
auto mask = Ui::RippleAnimation::roundRectMask(maskSize, st::buttonRadius);
|
||||
return MakeShared<Ui::RippleAnimation>(st::stickersTrendingAdd.ripple, std::move(mask), [this, section] {
|
||||
rtlupdate(featuredAddRect(section));
|
||||
});
|
||||
return std::make_unique<Ui::RippleAnimation>(
|
||||
st::stickersTrendingAdd.ripple,
|
||||
std::move(mask),
|
||||
[this, section] { rtlupdate(featuredAddRect(section)); });
|
||||
}
|
||||
auto maskSize = QSize(st::stickerPanRemoveSet.rippleAreaSize, st::stickerPanRemoveSet.rippleAreaSize);
|
||||
auto mask = Ui::RippleAnimation::ellipseMask(maskSize);
|
||||
return MakeShared<Ui::RippleAnimation>(st::stickerPanRemoveSet.ripple, std::move(mask), [this, section] {
|
||||
rtlupdate(removeButtonRect(section));
|
||||
});
|
||||
return std::make_unique<Ui::RippleAnimation>(
|
||||
st::stickerPanRemoveSet.ripple,
|
||||
std::move(mask),
|
||||
[this, section] { rtlupdate(removeButtonRect(section)); });
|
||||
}
|
||||
|
||||
QPoint StickersListWidget::buttonRippleTopLeft(int section) const {
|
||||
@@ -1083,14 +1104,16 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
}
|
||||
|
||||
void StickersListWidget::removeRecentSticker(int section, int index) {
|
||||
if (_section != Section::Stickers || section >= _mySets.size() || _mySets[section].id != Stickers::RecentSetId) {
|
||||
if ((_section != Section::Stickers)
|
||||
|| (section >= int(_mySets.size()))
|
||||
|| (_mySets[section].id != Stickers::RecentSetId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearSelection();
|
||||
bool refresh = false;
|
||||
auto sticker = _mySets[section].pack[index];
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = Stickers::GetRecentPack();
|
||||
for (int32 i = 0, l = recent.size(); i < l; ++i) {
|
||||
if (recent.at(i).first == sticker) {
|
||||
recent.removeAt(i);
|
||||
@@ -1122,7 +1145,9 @@ void StickersListWidget::removeRecentSticker(int section, int index) {
|
||||
}
|
||||
|
||||
void StickersListWidget::removeFavedSticker(int section, int index) {
|
||||
if (_section != Section::Stickers || section >= _mySets.size() || _mySets[section].id != Stickers::FavedSetId) {
|
||||
if ((_section != Section::Stickers)
|
||||
|| (section >= int(_mySets.size()))
|
||||
|| (_mySets[section].id != Stickers::FavedSetId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1229,8 +1254,7 @@ void StickersListWidget::refreshStickers() {
|
||||
}
|
||||
|
||||
void StickersListWidget::refreshSettingsVisibility() {
|
||||
auto visible = (_section == Section::Stickers)
|
||||
&& _mySets.isEmpty();
|
||||
const auto visible = (_section == Section::Stickers) && _mySets.empty();
|
||||
_settings->setVisible(visible);
|
||||
}
|
||||
|
||||
@@ -1251,7 +1275,7 @@ void StickersListWidget::preloadImages() {
|
||||
for (int j = 0; j != count; ++j) {
|
||||
if (++k > _columnCount * (_columnCount + 1)) break;
|
||||
|
||||
auto sticker = sets.at(i).pack.at(j);
|
||||
auto sticker = sets[i].pack.at(j);
|
||||
if (!sticker || !sticker->sticker()) continue;
|
||||
|
||||
bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128));
|
||||
@@ -1272,10 +1296,15 @@ uint64 StickersListWidget::currentSet(int yOffset) const {
|
||||
if (_section == Section::Featured) {
|
||||
return Stickers::FeaturedSetId;
|
||||
}
|
||||
return _mySets.isEmpty() ? Stickers::RecentSetId : _mySets[sectionInfoByOffset(yOffset).section].id;
|
||||
return _mySets.empty()
|
||||
? Stickers::RecentSetId
|
||||
: _mySets[sectionInfoByOffset(yOffset).section].id;
|
||||
}
|
||||
|
||||
void StickersListWidget::appendSet(Sets &to, uint64 setId, AppendSkip skip) {
|
||||
void StickersListWidget::appendSet(
|
||||
std::vector<Set> &to,
|
||||
uint64 setId,
|
||||
AppendSkip skip) {
|
||||
auto &sets = Auth().data().stickerSets();
|
||||
auto it = sets.constFind(setId);
|
||||
if (it == sets.cend() || it->stickers.isEmpty()) return;
|
||||
@@ -1302,7 +1331,7 @@ void StickersListWidget::refreshRecentStickers(bool performResize) {
|
||||
_custom.clear();
|
||||
clearSelection();
|
||||
auto &sets = Auth().data().stickerSets();
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = Stickers::GetRecentPack();
|
||||
auto customIt = sets.constFind(Stickers::CustomSetId);
|
||||
auto cloudIt = sets.constFind(Stickers::CloudRecentSetId);
|
||||
|
||||
@@ -1423,7 +1452,7 @@ void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) {
|
||||
void StickersListWidget::fillIcons(QList<StickerIcon> &icons) {
|
||||
icons.clear();
|
||||
icons.reserve(_mySets.size() + 1);
|
||||
if (Auth().data().featuredStickerSetsUnreadCount() && !_featuredSets.isEmpty()) {
|
||||
if (Auth().data().featuredStickerSetsUnreadCount() && !_featuredSets.empty()) {
|
||||
icons.push_back(StickerIcon(Stickers::FeaturedSetId));
|
||||
}
|
||||
|
||||
@@ -1457,7 +1486,8 @@ void StickersListWidget::fillIcons(QList<StickerIcon> &icons) {
|
||||
icons.push_back(StickerIcon(_mySets[i].id, s, pixw, pixh));
|
||||
}
|
||||
|
||||
if (!Auth().data().featuredStickerSetsUnreadCount() && !_featuredSets.isEmpty()) {
|
||||
if (!Auth().data().featuredStickerSetsUnreadCount()
|
||||
&& !_featuredSets.empty()) {
|
||||
icons.push_back(StickerIcon(Stickers::FeaturedSetId));
|
||||
}
|
||||
}
|
||||
@@ -1773,7 +1803,7 @@ void StickersListWidget::removeSet(uint64 setId) {
|
||||
request(MTPmessages_UninstallStickerSet(MTP_inputStickerSetShortName(MTP_string(it->shortName)))).send();
|
||||
}
|
||||
auto writeRecent = false;
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto &recent = Stickers::GetRecentPack();
|
||||
for (auto i = recent.begin(); i != recent.cend();) {
|
||||
if (it->stickers.indexOf(i->first) >= 0) {
|
||||
i = recent.erase(i);
|
||||
|
||||
@@ -138,15 +138,22 @@ private:
|
||||
};
|
||||
|
||||
struct Set {
|
||||
Set(uint64 id, MTPDstickerSet::Flags flags, const QString &title, int32 hoversSize, const Stickers::Pack &pack = Stickers::Pack()) : id(id), flags(flags), title(title), pack(pack) {
|
||||
}
|
||||
Set(
|
||||
uint64 id,
|
||||
MTPDstickerSet::Flags flags,
|
||||
const QString &title,
|
||||
int hoversSize,
|
||||
const Stickers::Pack &pack = Stickers::Pack());
|
||||
Set(Set &&other);
|
||||
Set &operator=(Set &&other);
|
||||
~Set();
|
||||
|
||||
uint64 id;
|
||||
MTPDstickerSet::Flags flags;
|
||||
QString title;
|
||||
Stickers::Pack pack;
|
||||
QSharedPointer<Ui::RippleAnimation> ripple;
|
||||
std::unique_ptr<Ui::RippleAnimation> ripple;
|
||||
};
|
||||
using Sets = QList<Set>;
|
||||
|
||||
template <typename Callback>
|
||||
bool enumerateSections(Callback callback) const;
|
||||
@@ -172,7 +179,7 @@ private:
|
||||
void updateSelected();
|
||||
void setSelected(OverState newSelected);
|
||||
void setPressed(OverState newPressed);
|
||||
QSharedPointer<Ui::RippleAnimation> createButtonRipple(int section);
|
||||
std::unique_ptr<Ui::RippleAnimation> createButtonRipple(int section);
|
||||
QPoint buttonRippleTopLeft(int section) const;
|
||||
|
||||
enum class ValidateIconAnimations {
|
||||
@@ -182,10 +189,10 @@ private:
|
||||
};
|
||||
void validateSelectedIcon(ValidateIconAnimations animations);
|
||||
|
||||
Sets &shownSets() {
|
||||
std::vector<Set> &shownSets() {
|
||||
return (_section == Section::Featured) ? _featuredSets : _mySets;
|
||||
}
|
||||
const Sets &shownSets() const {
|
||||
const std::vector<Set> &shownSets() const {
|
||||
return (_section == Section::Featured) ? _featuredSets : _mySets;
|
||||
}
|
||||
int featuredRowHeight() const;
|
||||
@@ -210,7 +217,10 @@ private:
|
||||
Archived,
|
||||
Installed,
|
||||
};
|
||||
void appendSet(Sets &to, uint64 setId, AppendSkip skip = AppendSkip::None);
|
||||
void appendSet(
|
||||
std::vector<Set> &to,
|
||||
uint64 setId,
|
||||
AppendSkip skip = AppendSkip::None);
|
||||
|
||||
void selectEmoji(EmojiPtr emoji);
|
||||
int stickersLeft() const;
|
||||
@@ -222,10 +232,10 @@ private:
|
||||
void refreshFooterIcons();
|
||||
|
||||
ChannelData *_megagroupSet = nullptr;
|
||||
Sets _mySets;
|
||||
Sets _featuredSets;
|
||||
OrderedSet<uint64> _installedLocallySets;
|
||||
QList<bool> _custom;
|
||||
std::vector<Set> _mySets;
|
||||
std::vector<Set> _featuredSets;
|
||||
base::flat_set<uint64> _installedLocallySets;
|
||||
std::vector<bool> _custom;
|
||||
base::flat_set<not_null<DocumentData*>> _favedStickersMap;
|
||||
|
||||
Section _section = Section::Stickers;
|
||||
|
||||
@@ -63,10 +63,10 @@ TabbedPanel::TabbedPanel(
|
||||
_controller->disableGifPauseReason(Window::GifPauseReason::SavedGifs);
|
||||
}
|
||||
});
|
||||
_selector->showRequests()
|
||||
| rpl::start_with_next([this] {
|
||||
this->showFromSelector();
|
||||
}, lifetime());
|
||||
_selector->showRequests(
|
||||
) | rpl::start_with_next([this] {
|
||||
this->showFromSelector();
|
||||
}, lifetime());
|
||||
|
||||
resize(QRect(0, 0, st::emojiPanWidth, st::emojiPanMaxHeight).marginsAdded(innerPadding()).size());
|
||||
|
||||
@@ -255,7 +255,7 @@ void TabbedPanel::prepareCache() {
|
||||
auto showAnimation = base::take(_a_show);
|
||||
auto showAnimationData = base::take(_showAnimation);
|
||||
showChildren();
|
||||
_cache = myGrab(this);
|
||||
_cache = Ui::GrabWidget(this);
|
||||
_showAnimation = base::take(showAnimationData);
|
||||
_a_show = base::take(showAnimation);
|
||||
if (_a_show.animating()) {
|
||||
@@ -296,7 +296,7 @@ QImage TabbedPanel::grabForAnimation() {
|
||||
auto showAnimation = base::take(_a_show);
|
||||
|
||||
showChildren();
|
||||
myEnsureResized(this);
|
||||
Ui::SendPendingMoveResizeEvents(this);
|
||||
|
||||
auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(cRetinaFactor());
|
||||
|
||||
@@ -339,13 +339,13 @@ TabbedSelector::TabbedSelector(QWidget *parent, not_null<Window::Controller*> co
|
||||
}
|
||||
}));
|
||||
|
||||
Auth().api().stickerSetInstalled()
|
||||
| rpl::start_with_next([this](uint64 setId) {
|
||||
_tabsSlider->setActiveSection(
|
||||
static_cast<int>(SelectorTab::Stickers));
|
||||
stickers()->showStickerSet(setId);
|
||||
_showRequests.fire({});
|
||||
}, lifetime());
|
||||
Auth().api().stickerSetInstalled(
|
||||
) | rpl::start_with_next([this](uint64 setId) {
|
||||
_tabsSlider->setActiveSection(
|
||||
static_cast<int>(SelectorTab::Stickers));
|
||||
stickers()->showStickerSet(setId);
|
||||
_showRequests.fire({});
|
||||
}, lifetime());
|
||||
|
||||
// setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||
@@ -489,7 +489,7 @@ QImage TabbedSelector::grabForAnimation() {
|
||||
showAll();
|
||||
_topShadow->hide();
|
||||
_tabsSlider->hide();
|
||||
myEnsureResized(this);
|
||||
Ui::SendPendingMoveResizeEvents(this);
|
||||
|
||||
auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(cRetinaFactor());
|
||||
@@ -638,10 +638,10 @@ void TabbedSelector::createTabsSlider() {
|
||||
_tabsSlider->setSections(sections);
|
||||
|
||||
_tabsSlider->setActiveSectionFast(static_cast<int>(_currentTabType));
|
||||
_tabsSlider->sectionActivated()
|
||||
| rpl::start_with_next(
|
||||
[this] { switchTab(); },
|
||||
lifetime());
|
||||
_tabsSlider->sectionActivated(
|
||||
) | rpl::start_with_next(
|
||||
[this] { switchTab(); },
|
||||
lifetime());
|
||||
}
|
||||
|
||||
bool TabbedSelector::hasSectionIcons() const {
|
||||
|
||||
@@ -1191,9 +1191,11 @@ QByteArray iconMaskValueSize(int width, int height) {
|
||||
QByteArray iconMaskValuePng(QString filepath) {
|
||||
QByteArray result;
|
||||
|
||||
auto pathAndModifiers = filepath.split('-');
|
||||
filepath = pathAndModifiers[0];
|
||||
auto modifiers = pathAndModifiers.mid(1);
|
||||
QFileInfo fileInfo(filepath);
|
||||
auto directory = fileInfo.dir();
|
||||
auto nameAndModifiers = fileInfo.fileName().split('-');
|
||||
filepath = directory.filePath(nameAndModifiers[0]);
|
||||
auto modifiers = nameAndModifiers.mid(1);
|
||||
|
||||
QImage png100x(filepath + ".png");
|
||||
QImage png200x(filepath + "@2x.png");
|
||||
|
||||
@@ -285,8 +285,6 @@ enum {
|
||||
DialogsFirstLoad = 20, // first dialogs part size requested
|
||||
DialogsPerPage = 500, // next dialogs part size
|
||||
|
||||
FileLoaderQueueStopTimeout = 5000,
|
||||
|
||||
UseBigFilesFrom = 10 * 1024 * 1024, // mtp big files methods used for files greater than 10mb
|
||||
|
||||
UploadPartSize = 32 * 1024, // 32kb for photo
|
||||
|
||||
197
Telegram/SourceFiles/core/changelogs.cpp
Normal file
197
Telegram/SourceFiles/core/changelogs.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "core/changelogs.h"
|
||||
|
||||
#include "storage/localstorage.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
std::map<int, const char*> AlphaLogs() {
|
||||
return {
|
||||
{
|
||||
1001024,
|
||||
"\xE2\x80\x94 Radically improved navigation. "
|
||||
"New side panel on the right with quick access to "
|
||||
"shared media and group members.\n"
|
||||
|
||||
"\xE2\x80\x94 Pinned Messages. If you are a channel admin, "
|
||||
"pin messages to focus your subscribers\xE2\x80\x99 attention "
|
||||
"on important announcements.\n"
|
||||
|
||||
"\xE2\x80\x94 Also supported clearing history in supergroups "
|
||||
"and added a host of minor improvements."
|
||||
},
|
||||
{
|
||||
1001026,
|
||||
"\xE2\x80\x94 Admin badges in supergroup messages.\n"
|
||||
"\xE2\x80\x94 Fix crashing on launch in OS X 10.6.\n"
|
||||
"\xE2\x80\x94 Bug fixes and other minor improvements."
|
||||
},
|
||||
{
|
||||
1001027,
|
||||
"\xE2\x80\x94 Saved Messages. Bookmark messages by forwarding them "
|
||||
"to \xE2\x80\x9C""Saved Messages\xE2\x80\x9D. "
|
||||
"Access them from the Chats list or from the side menu."
|
||||
},
|
||||
{
|
||||
1002002,
|
||||
"\xE2\x80\x94 Grouped photos and videos are displayed as albums."
|
||||
},
|
||||
{
|
||||
1002004,
|
||||
"\xE2\x80\x94 Group media into an album "
|
||||
"when sharing multiple photos and videos.\n"
|
||||
|
||||
"\xE2\x80\x94 Bug fixes and other minor improvements."
|
||||
},
|
||||
{
|
||||
1002005,
|
||||
"\xE2\x80\x94 When viewing a photo from an album, "
|
||||
"you'll see other pictures from the same group "
|
||||
"as thumbnails in the lower part of the screen.\n"
|
||||
|
||||
"\xE2\x80\x94 When composing an album paste "
|
||||
"additional media from the clipboard.\n"
|
||||
|
||||
"\xE2\x80\x94 Bug fixes and other minor improvements."
|
||||
},
|
||||
{
|
||||
1002007,
|
||||
"\xE2\x80\x94 Use fast reply button in group chats.\n"
|
||||
|
||||
"\xE2\x80\x94 Select a message you want to reply to by "
|
||||
"pressing Ctrl+Up and Ctrl+Down."
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
QString FormatVersionDisplay(int version) {
|
||||
return QString::number(version / 1000000)
|
||||
+ '.' + QString::number((version % 1000000) / 1000)
|
||||
+ ((version % 1000)
|
||||
? ('.' + QString::number(version % 1000))
|
||||
: QString());
|
||||
}
|
||||
|
||||
QString FormatVersionPrecise(int version) {
|
||||
return QString::number(version / 1000000)
|
||||
+ '.' + QString::number((version % 1000000) / 1000)
|
||||
+ '.' + QString::number(version % 1000);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Changelogs::Changelogs(not_null<AuthSession*> session, int oldVersion)
|
||||
: _session(session)
|
||||
, _oldVersion(oldVersion) {
|
||||
_chatsSubscription = subscribe(
|
||||
_session->data().moreChatsLoaded(),
|
||||
[this] { requestCloudLogs(); });
|
||||
}
|
||||
|
||||
std::unique_ptr<Changelogs> Changelogs::Create(
|
||||
not_null<AuthSession*> session) {
|
||||
const auto oldVersion = Local::oldMapVersion();
|
||||
return (oldVersion > 0 && oldVersion < AppVersion)
|
||||
? std::make_unique<Changelogs>(session, oldVersion)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void Changelogs::requestCloudLogs() {
|
||||
unsubscribe(base::take(_chatsSubscription));
|
||||
|
||||
const auto callback = [this](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
|
||||
auto resultEmpty = true;
|
||||
switch (result.type()) {
|
||||
case mtpc_updateShortMessage:
|
||||
case mtpc_updateShortChatMessage:
|
||||
case mtpc_updateShort:
|
||||
resultEmpty = false;
|
||||
break;
|
||||
case mtpc_updatesCombined:
|
||||
resultEmpty = result.c_updatesCombined().vupdates.v.isEmpty();
|
||||
break;
|
||||
case mtpc_updates:
|
||||
resultEmpty = result.c_updates().vupdates.v.isEmpty();
|
||||
break;
|
||||
case mtpc_updatesTooLong:
|
||||
case mtpc_updateShortSentMessage:
|
||||
LOG(("API Error: Bad updates type in app changelog."));
|
||||
break;
|
||||
}
|
||||
if (resultEmpty) {
|
||||
addLocalLogs();
|
||||
}
|
||||
};
|
||||
_session->api().requestChangelog(
|
||||
FormatVersionPrecise(_oldVersion),
|
||||
base::lambda_guarded(this, callback));
|
||||
}
|
||||
|
||||
void Changelogs::addLocalLogs() {
|
||||
if (cAlphaVersion() || cBetaVersion()) {
|
||||
addAlphaLogs();
|
||||
}
|
||||
if (!_addedSomeLocal) {
|
||||
const auto text = lng_new_version_wrap(
|
||||
lt_version,
|
||||
str_const_toString(AppVersionStr),
|
||||
lt_changes,
|
||||
lang(lng_new_version_minor),
|
||||
lt_link,
|
||||
qsl("https://desktop.telegram.org/changelog"));
|
||||
addLocalLog(text.trimmed());
|
||||
}
|
||||
}
|
||||
|
||||
void Changelogs::addLocalLog(const QString &text) {
|
||||
auto textWithEntities = TextWithEntities{ text };
|
||||
TextUtilities::ParseEntities(textWithEntities, TextParseLinks);
|
||||
App::wnd()->serviceNotification(
|
||||
textWithEntities,
|
||||
MTP_messageMediaEmpty(),
|
||||
unixtime());
|
||||
_addedSomeLocal = true;
|
||||
};
|
||||
|
||||
void Changelogs::addAlphaLogs() {
|
||||
for (const auto[version, changes] : AlphaLogs()) {
|
||||
addAlphaLog(version, changes);
|
||||
}
|
||||
}
|
||||
|
||||
void Changelogs::addAlphaLog(int changeVersion, const char *changes) {
|
||||
if (_oldVersion >= changeVersion) {
|
||||
return;
|
||||
}
|
||||
const auto version = FormatVersionDisplay(changeVersion);
|
||||
const auto text = qsl("New in version %1:\n\n").arg(version)
|
||||
+ QString::fromUtf8(changes).trimmed();
|
||||
addLocalLog(text);
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
50
Telegram/SourceFiles/core/changelogs.h
Normal file
50
Telegram/SourceFiles/core/changelogs.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
class AuthSession;
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Changelogs : public base::has_weak_ptr, private base::Subscriber {
|
||||
public:
|
||||
Changelogs(not_null<AuthSession*> session, int oldVersion);
|
||||
|
||||
static std::unique_ptr<Changelogs> Create(
|
||||
not_null<AuthSession*> session);
|
||||
|
||||
private:
|
||||
void requestCloudLogs();
|
||||
void addLocalLogs();
|
||||
void addLocalLog(const QString &text);
|
||||
void addAlphaLogs();
|
||||
void addAlphaLog(int changeVersion, const char *changes);
|
||||
|
||||
const not_null<AuthSession*> _session;
|
||||
const int _oldVersion = 0;
|
||||
int _chatsSubscription = 0;
|
||||
bool _addedSomeLocal = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -38,9 +38,11 @@ bool ClickHandler::setActive(const ClickHandlerPtr &p, ClickHandlerHost *host) {
|
||||
// other pressed click handler currently, if there is
|
||||
// this method will be called when it is unpressed
|
||||
if (_active && *_active) {
|
||||
auto emitClickHandlerActiveChanged = (!_pressed || !*_pressed || *_pressed == *_active);
|
||||
auto wasactive = *_active;
|
||||
(*_active).clear();
|
||||
const auto emitClickHandlerActiveChanged = false
|
||||
|| !_pressed
|
||||
|| !*_pressed
|
||||
|| (*_pressed == *_active);
|
||||
const auto wasactive = base::take(*_active);
|
||||
if (_activeHost) {
|
||||
if (emitClickHandlerActiveChanged) {
|
||||
_activeHost->clickHandlerActiveChanged(wasactive, false);
|
||||
|
||||
@@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#pragma once
|
||||
|
||||
class ClickHandler;
|
||||
using ClickHandlerPtr = QSharedPointer<ClickHandler>;
|
||||
using ClickHandlerPtr = std::shared_ptr<ClickHandler>;
|
||||
|
||||
enum ExpandLinksMode {
|
||||
ExpandLinksNone,
|
||||
@@ -103,9 +103,8 @@ public:
|
||||
// The activated click handler (if any) is returned.
|
||||
static ClickHandlerPtr unpressed() {
|
||||
if (_pressed && *_pressed) {
|
||||
bool activated = (_active && *_active == *_pressed);
|
||||
ClickHandlerPtr waspressed = *_pressed;
|
||||
(*_pressed).clear();
|
||||
const auto activated = (_active && *_active == *_pressed);
|
||||
const auto waspressed = base::take(*_pressed);
|
||||
if (_pressedHost) {
|
||||
_pressedHost->clickHandlerPressedChanged(waspressed, false);
|
||||
_pressedHost = nullptr;
|
||||
@@ -144,11 +143,15 @@ public:
|
||||
}
|
||||
static void hostDestroyed(ClickHandlerHost *host) {
|
||||
if (_activeHost == host) {
|
||||
if (_active) (*_active).clear();
|
||||
if (_active) {
|
||||
*_active = nullptr;
|
||||
}
|
||||
_activeHost = nullptr;
|
||||
}
|
||||
if (_pressedHost == host) {
|
||||
if (_pressed) (*_pressed).clear();
|
||||
if (_pressed) {
|
||||
*_pressed = nullptr;
|
||||
}
|
||||
_pressedHost = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ void HiddenUrlClickHandler::doOpen(QString url) {
|
||||
Ui::show(Box<ConfirmBox>(lang(lng_open_this_link) + qsl("\n\n") + displayUrl, lang(lng_open_link), [urlText] {
|
||||
Ui::hideLayer();
|
||||
UrlClickHandler::doOpen(urlText);
|
||||
}));
|
||||
}), LayerOption::KeepOther);
|
||||
} else {
|
||||
UrlClickHandler::doOpen(urlText);
|
||||
}
|
||||
|
||||
@@ -97,14 +97,15 @@ private:
|
||||
QString _originalUrl, _readable;
|
||||
|
||||
};
|
||||
typedef QSharedPointer<TextClickHandler> TextClickHandlerPtr;
|
||||
|
||||
class HiddenUrlClickHandler : public UrlClickHandler {
|
||||
public:
|
||||
HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) {
|
||||
}
|
||||
QString copyToClipboardContextItemText() const override {
|
||||
return url().isEmpty() ? QString() : UrlClickHandler::copyToClipboardContextItemText();
|
||||
return url().isEmpty()
|
||||
? QString()
|
||||
: UrlClickHandler::copyToClipboardContextItemText();
|
||||
}
|
||||
|
||||
static void doOpen(QString url);
|
||||
|
||||
1080
Telegram/SourceFiles/core/crash_report_window.cpp
Normal file
1080
Telegram/SourceFiles/core/crash_report_window.cpp
Normal file
File diff suppressed because it is too large
Load Diff
226
Telegram/SourceFiles/core/crash_report_window.h
Normal file
226
Telegram/SourceFiles/core/crash_report_window.h
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class PreLaunchWindow : public QWidget {
|
||||
public:
|
||||
PreLaunchWindow(QString title = QString());
|
||||
void activate();
|
||||
int basicSize() const {
|
||||
return _size;
|
||||
}
|
||||
~PreLaunchWindow();
|
||||
|
||||
static PreLaunchWindow *instance();
|
||||
|
||||
protected:
|
||||
|
||||
int _size;
|
||||
|
||||
};
|
||||
|
||||
class PreLaunchLabel : public QLabel {
|
||||
public:
|
||||
PreLaunchLabel(QWidget *parent);
|
||||
void setText(const QString &text);
|
||||
|
||||
};
|
||||
|
||||
class PreLaunchInput : public QLineEdit {
|
||||
public:
|
||||
PreLaunchInput(QWidget *parent, bool password = false);
|
||||
|
||||
};
|
||||
|
||||
class PreLaunchLog : public QTextEdit {
|
||||
public:
|
||||
PreLaunchLog(QWidget *parent);
|
||||
|
||||
};
|
||||
|
||||
class PreLaunchButton : public QPushButton {
|
||||
public:
|
||||
PreLaunchButton(QWidget *parent, bool confirm = true);
|
||||
void setText(const QString &text);
|
||||
|
||||
};
|
||||
|
||||
class PreLaunchCheckbox : public QCheckBox {
|
||||
public:
|
||||
PreLaunchCheckbox(QWidget *parent);
|
||||
void setText(const QString &text);
|
||||
|
||||
};
|
||||
|
||||
class NotStartedWindow : public PreLaunchWindow {
|
||||
public:
|
||||
NotStartedWindow();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
|
||||
private:
|
||||
void updateControls();
|
||||
|
||||
PreLaunchLabel _label;
|
||||
PreLaunchLog _log;
|
||||
PreLaunchButton _close;
|
||||
|
||||
};
|
||||
|
||||
class LastCrashedWindow : public PreLaunchWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LastCrashedWindow();
|
||||
|
||||
public slots:
|
||||
void onViewReport();
|
||||
void onSaveReport();
|
||||
void onSendReport();
|
||||
void onGetApp();
|
||||
|
||||
void onNetworkSettings();
|
||||
void onNetworkSettingsSaved(QString host, quint32 port, QString username, QString password);
|
||||
void onContinue();
|
||||
|
||||
void onCheckingFinished();
|
||||
void onSendingError(QNetworkReply::NetworkError e);
|
||||
void onSendingFinished();
|
||||
void onSendingProgress(qint64 uploaded, qint64 total);
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
|
||||
void onUpdateRetry();
|
||||
void onUpdateSkip();
|
||||
|
||||
void onUpdateChecking();
|
||||
void onUpdateLatest();
|
||||
void onUpdateDownloading(qint64 ready, qint64 total);
|
||||
void onUpdateReady();
|
||||
void onUpdateFailed();
|
||||
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
|
||||
private:
|
||||
QString minidumpFileName();
|
||||
void updateControls();
|
||||
|
||||
QString _host, _username, _password;
|
||||
quint32 _port;
|
||||
|
||||
PreLaunchLabel _label, _pleaseSendReport, _yourReportName, _minidump;
|
||||
PreLaunchLog _report;
|
||||
PreLaunchButton _send, _sendSkip, _networkSettings, _continue, _showReport, _saveReport, _getApp;
|
||||
PreLaunchCheckbox _includeUsername;
|
||||
|
||||
QString _minidumpName, _minidumpFull, _reportText;
|
||||
QString _reportUsername, _reportTextNoUsername;
|
||||
QByteArray getCrashReportRaw() const;
|
||||
|
||||
bool _reportShown, _reportSaved;
|
||||
|
||||
void excludeReportUsername();
|
||||
|
||||
enum SendingState {
|
||||
SendingNoReport,
|
||||
SendingUpdateCheck,
|
||||
SendingNone,
|
||||
SendingTooOld,
|
||||
SendingTooMany,
|
||||
SendingUnofficial,
|
||||
SendingProgress,
|
||||
SendingUploading,
|
||||
SendingFail,
|
||||
SendingDone,
|
||||
};
|
||||
SendingState _sendingState;
|
||||
|
||||
PreLaunchLabel _updating;
|
||||
qint64 _sendingProgress, _sendingTotal;
|
||||
|
||||
QNetworkAccessManager _sendManager;
|
||||
QNetworkReply *_checkReply, *_sendReply;
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
|
||||
PreLaunchButton _updatingCheck, _updatingSkip;
|
||||
enum UpdatingState {
|
||||
UpdatingNone,
|
||||
UpdatingCheck,
|
||||
UpdatingLatest,
|
||||
UpdatingDownload,
|
||||
UpdatingFail,
|
||||
UpdatingReady
|
||||
};
|
||||
UpdatingState _updatingState;
|
||||
QString _newVersionDownload;
|
||||
|
||||
void setUpdatingState(UpdatingState state, bool force = false);
|
||||
void setDownloadProgress(qint64 ready, qint64 total);
|
||||
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
|
||||
|
||||
QString getReportField(const QLatin1String &name, const QLatin1String &prefix);
|
||||
void addReportFieldPart(const QLatin1String &name, const QLatin1String &prefix, QHttpMultiPart *multipart);
|
||||
|
||||
};
|
||||
|
||||
class NetworkSettingsWindow : public PreLaunchWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
NetworkSettingsWindow(QWidget *parent, QString host, quint32 port, QString username, QString password);
|
||||
|
||||
signals:
|
||||
void saved(QString host, quint32 port, QString username, QString password);
|
||||
|
||||
public slots:
|
||||
void onSave();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
|
||||
private:
|
||||
void updateControls();
|
||||
|
||||
PreLaunchLabel _hostLabel, _portLabel, _usernameLabel, _passwordLabel;
|
||||
PreLaunchInput _hostInput, _portInput, _usernameInput, _passwordInput;
|
||||
PreLaunchButton _save, _cancel;
|
||||
|
||||
QWidget *_parent;
|
||||
|
||||
};
|
||||
|
||||
class ShowCrashReportWindow : public PreLaunchWindow {
|
||||
public:
|
||||
ShowCrashReportWindow(const QString &text);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
void closeEvent(QCloseEvent *e);
|
||||
|
||||
private:
|
||||
PreLaunchLog _log;
|
||||
|
||||
};
|
||||
580
Telegram/SourceFiles/core/crash_reports.cpp
Normal file
580
Telegram/SourceFiles/core/crash_reports.cpp
Normal file
@@ -0,0 +1,580 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "core/crash_reports.h"
|
||||
|
||||
#include "platform/platform_specific.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <new>
|
||||
#include <mutex>
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
// see https://blog.inventic.eu/2012/08/qt-and-google-breakpad/
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4091)
|
||||
#include "client/windows/handler/exception_handler.h"
|
||||
#pragma warning(pop)
|
||||
|
||||
#elif defined Q_OS_MAC // Q_OS_WIN
|
||||
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef MAC_USE_BREAKPAD
|
||||
#include "client/mac/handler/exception_handler.h"
|
||||
#else // MAC_USE_BREAKPAD
|
||||
#include "client/crashpad_client.h"
|
||||
#endif // else for MAC_USE_BREAKPAD
|
||||
|
||||
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC
|
||||
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
|
||||
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
namespace CrashReports {
|
||||
namespace {
|
||||
|
||||
using Annotations = std::map<std::string, std::string>;
|
||||
using AnnotationRefs = std::map<std::string, const QString*>;
|
||||
|
||||
Annotations ProcessAnnotations;
|
||||
AnnotationRefs ProcessAnnotationRefs;
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
QString ReportPath;
|
||||
FILE *ReportFile = nullptr;
|
||||
int ReportFileNo = 0;
|
||||
char LaunchedDateTimeStr[32] = { 0 };
|
||||
char LaunchedBinaryName[256] = { 0 };
|
||||
|
||||
void SafeWriteChar(char ch) {
|
||||
fwrite(&ch, 1, 1, ReportFile);
|
||||
}
|
||||
|
||||
template <bool Unsigned, typename Type>
|
||||
struct writeNumberSignAndRemoveIt {
|
||||
static void call(Type &number) {
|
||||
if (number < 0) {
|
||||
SafeWriteChar('-');
|
||||
number = -number;
|
||||
}
|
||||
}
|
||||
};
|
||||
template <typename Type>
|
||||
struct writeNumberSignAndRemoveIt<true, Type> {
|
||||
static void call(Type &number) {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Type>
|
||||
const dump &SafeWriteNumber(const dump &stream, Type number) {
|
||||
if (!ReportFile) return stream;
|
||||
|
||||
writeNumberSignAndRemoveIt<(Type(-1) > Type(0)), Type>::call(number);
|
||||
Type upper = 1, prev = number / 10;
|
||||
while (prev >= upper) {
|
||||
upper *= 10;
|
||||
}
|
||||
while (upper > 0) {
|
||||
int digit = (number / upper);
|
||||
SafeWriteChar('0' + digit);
|
||||
number -= digit * upper;
|
||||
upper /= 10;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
using ReservedMemoryChunk = std::array<gsl::byte, 1024 * 1024>;
|
||||
std::unique_ptr<ReservedMemoryChunk> ReservedMemory;
|
||||
|
||||
void InstallOperatorNewHandler() {
|
||||
ReservedMemory = std::make_unique<ReservedMemoryChunk>();
|
||||
std::set_new_handler([] {
|
||||
std::set_new_handler(nullptr);
|
||||
ReservedMemory.reset();
|
||||
Unexpected("Could not allocate!");
|
||||
});
|
||||
}
|
||||
|
||||
Qt::HANDLE ReportingThreadId = nullptr;
|
||||
bool ReportingHeaderWritten = false;
|
||||
QMutex ReportingMutex;
|
||||
|
||||
const char *BreakpadDumpPath = nullptr;
|
||||
const wchar_t *BreakpadDumpPathW = nullptr;
|
||||
|
||||
#if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64
|
||||
struct sigaction SIG_def[32];
|
||||
|
||||
void SignalHandler(int signum, siginfo_t *info, void *ucontext) {
|
||||
if (signum > 0) {
|
||||
sigaction(signum, &SIG_def[signum], 0);
|
||||
}
|
||||
|
||||
#else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
|
||||
void SignalHandler(int signum) {
|
||||
#endif // else for Q_OS_MAC || Q_OS_LINUX || Q_OS_LINUX64
|
||||
|
||||
const char* name = 0;
|
||||
switch (signum) {
|
||||
case SIGABRT: name = "SIGABRT"; break;
|
||||
case SIGSEGV: name = "SIGSEGV"; break;
|
||||
case SIGILL: name = "SIGILL"; break;
|
||||
case SIGFPE: name = "SIGFPE"; break;
|
||||
#ifndef Q_OS_WIN
|
||||
case SIGBUS: name = "SIGBUS"; break;
|
||||
case SIGSYS: name = "SIGSYS"; break;
|
||||
#endif // !Q_OS_WIN
|
||||
}
|
||||
|
||||
Qt::HANDLE thread = QThread::currentThreadId();
|
||||
if (thread == ReportingThreadId) return;
|
||||
|
||||
QMutexLocker lock(&ReportingMutex);
|
||||
ReportingThreadId = thread;
|
||||
|
||||
if (!ReportingHeaderWritten) {
|
||||
ReportingHeaderWritten = true;
|
||||
auto dec2hex = [](int value) -> char {
|
||||
if (value >= 0 && value < 10) {
|
||||
return '0' + value;
|
||||
} else if (value >= 10 && value < 16) {
|
||||
return 'a' + (value - 10);
|
||||
}
|
||||
return '#';
|
||||
};
|
||||
|
||||
for (const auto &i : ProcessAnnotationRefs) {
|
||||
QByteArray utf8 = i.second->toUtf8();
|
||||
std::string wrapped;
|
||||
wrapped.reserve(4 * utf8.size());
|
||||
for (auto ch : utf8) {
|
||||
auto uch = static_cast<uchar>(ch);
|
||||
wrapped.append("\\x", 2).append(1, dec2hex(uch >> 4)).append(1, dec2hex(uch & 0x0F));
|
||||
}
|
||||
ProcessAnnotations[i.first] = wrapped;
|
||||
}
|
||||
|
||||
const Annotations c_ProcessAnnotations(ProcessAnnotations);
|
||||
for (const auto &i : c_ProcessAnnotations) {
|
||||
dump() << i.first.c_str() << ": " << i.second.c_str() << "\n";
|
||||
}
|
||||
psWriteDump();
|
||||
dump() << "\n";
|
||||
}
|
||||
if (name) {
|
||||
dump() << "Caught signal " << signum << " (" << name << ") in thread " << uint64(thread) << "\n";
|
||||
} else if (signum == -1) {
|
||||
dump() << "Google Breakpad caught a crash, minidump written in thread " << uint64(thread) << "\n";
|
||||
if (BreakpadDumpPath) {
|
||||
dump() << "Minidump: " << BreakpadDumpPath << "\n";
|
||||
} else if (BreakpadDumpPathW) {
|
||||
dump() << "Minidump: " << BreakpadDumpPathW << "\n";
|
||||
}
|
||||
} else {
|
||||
dump() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n";
|
||||
}
|
||||
|
||||
// see https://github.com/benbjohnson/bandicoot
|
||||
#if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64
|
||||
ucontext_t *uc = (ucontext_t*)ucontext;
|
||||
|
||||
void *caller = 0;
|
||||
if (uc) {
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
|
||||
/* OSX < 10.6 */
|
||||
#if defined(__x86_64__)
|
||||
caller = (void*)uc->uc_mcontext->__ss.__rip;
|
||||
#elif defined(__i386__)
|
||||
caller = (void*)uc->uc_mcontext->__ss.__eip;
|
||||
#else
|
||||
caller = (void*)uc->uc_mcontext->__ss.__srr0;
|
||||
#endif
|
||||
#elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
|
||||
/* OSX >= 10.6 */
|
||||
#if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
|
||||
caller = (void*)uc->uc_mcontext->__ss.__rip;
|
||||
#else
|
||||
caller = (void*)uc->uc_mcontext->__ss.__eip;
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
/* Linux */
|
||||
#if defined(__i386__)
|
||||
caller = (void*)uc->uc_mcontext.gregs[14]; /* Linux 32 */
|
||||
#elif defined(__X86_64__) || defined(__x86_64__)
|
||||
caller = (void*)uc->uc_mcontext.gregs[16]; /* Linux 64 */
|
||||
#elif defined(__ia64__) /* Linux IA64 */
|
||||
caller = (void*)uc->uc_mcontext.sc_ip;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void *addresses[132] = { 0 };
|
||||
size_t size = backtrace(addresses, 128);
|
||||
|
||||
/* overwrite sigaction with caller's address */
|
||||
if (caller) {
|
||||
for (int i = size; i > 1; --i) {
|
||||
addresses[i + 3] = addresses[i];
|
||||
}
|
||||
addresses[2] = (void*)0x1;
|
||||
addresses[3] = caller;
|
||||
addresses[4] = (void*)0x1;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
dump() << "\nBase image addresses:\n";
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
Dl_info info;
|
||||
dump() << i << " ";
|
||||
if (dladdr(addresses[i], &info)) {
|
||||
dump() << uint64(info.dli_fbase) << " (" << info.dli_fname << ")\n";
|
||||
} else {
|
||||
dump() << "_unknown_module_\n";
|
||||
}
|
||||
}
|
||||
#endif // Q_OS_MAC
|
||||
|
||||
dump() << "\nBacktrace:\n";
|
||||
|
||||
backtrace_symbols_fd(addresses, size, ReportFileNo);
|
||||
|
||||
#else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
|
||||
dump() << "\nBacktrace:\n";
|
||||
|
||||
psWriteStackTrace();
|
||||
#endif // else for Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
|
||||
|
||||
dump() << "\n";
|
||||
|
||||
ReportingThreadId = nullptr;
|
||||
}
|
||||
|
||||
bool SetSignalHandlers = true;
|
||||
bool CrashLogged = false;
|
||||
#if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
|
||||
google_breakpad::ExceptionHandler* BreakpadExceptionHandler = 0;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
bool DumpCallback(const wchar_t* _dump_dir, const wchar_t* _minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool success)
|
||||
#elif defined Q_OS_MAC // Q_OS_WIN
|
||||
bool DumpCallback(const char* _dump_dir, const char* _minidump_id, void *context, bool success)
|
||||
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC
|
||||
bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success)
|
||||
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
|
||||
{
|
||||
if (CrashLogged) return success;
|
||||
CrashLogged = true;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
BreakpadDumpPathW = _minidump_id;
|
||||
SignalHandler(-1);
|
||||
#else // Q_OS_WIN
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
BreakpadDumpPath = _minidump_id;
|
||||
#else // Q_OS_MAC
|
||||
BreakpadDumpPath = md.path();
|
||||
#endif // else for Q_OS_MAC
|
||||
SignalHandler(-1, 0, 0);
|
||||
#endif // else for Q_OS_WIN
|
||||
return success;
|
||||
}
|
||||
#endif // !Q_OS_MAC || MAC_USE_BREAKPAD
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
} // namespace
|
||||
|
||||
void StartCatching() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
ProcessAnnotations["Binary"] = cExeName().toUtf8().constData();
|
||||
ProcessAnnotations["ApiId"] = QString::number(ApiId).toUtf8().constData();
|
||||
ProcessAnnotations["Version"] = (cBetaVersion() ? qsl("%1 beta").arg(cBetaVersion()) : (cAlphaVersion() ? qsl("%1 alpha") : qsl("%1")).arg(AppVersion)).toUtf8().constData();
|
||||
ProcessAnnotations["Launched"] = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss").toUtf8().constData();
|
||||
ProcessAnnotations["Platform"] = cPlatformString().toUtf8().constData();
|
||||
ProcessAnnotations["UserTag"] = QString::number(Sandbox::UserTag(), 16).toUtf8().constData();
|
||||
|
||||
QString dumpspath = cWorkingDir() + qsl("tdata/dumps");
|
||||
QDir().mkpath(dumpspath);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
|
||||
dumpspath.toStdWString(),
|
||||
google_breakpad::ExceptionHandler::FilterCallback(nullptr),
|
||||
DumpCallback,
|
||||
(void*)nullptr, // callback_context
|
||||
google_breakpad::ExceptionHandler::HANDLER_ALL,
|
||||
MINIDUMP_TYPE(MiniDumpNormal),
|
||||
// MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithProcessThreadData | MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules | MiniDumpWithFullAuxiliaryState | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithTokenInformation),
|
||||
(const wchar_t*)nullptr, // pipe_name
|
||||
(const google_breakpad::CustomClientInfo*)nullptr
|
||||
);
|
||||
#elif defined Q_OS_MAC // Q_OS_WIN
|
||||
|
||||
#ifdef MAC_USE_BREAKPAD
|
||||
#ifndef _DEBUG
|
||||
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
|
||||
QFile::encodeName(dumpspath).toStdString(),
|
||||
/*FilterCallback*/ 0,
|
||||
DumpCallback,
|
||||
/*context*/ 0,
|
||||
true,
|
||||
0
|
||||
);
|
||||
#endif // !_DEBUG
|
||||
SetSignalHandlers = false;
|
||||
#else // MAC_USE_BREAKPAD
|
||||
crashpad::CrashpadClient crashpad_client;
|
||||
std::string handler = (cExeDir() + cExeName() + qsl("/Contents/Helpers/crashpad_handler")).toUtf8().constData();
|
||||
std::string database = QFile::encodeName(dumpspath).constData();
|
||||
if (crashpad_client.StartHandler(base::FilePath(handler),
|
||||
base::FilePath(database),
|
||||
std::string(),
|
||||
ProcessAnnotations,
|
||||
std::vector<std::string>(),
|
||||
false)) {
|
||||
crashpad_client.UseHandler();
|
||||
}
|
||||
#endif // else for MAC_USE_BREAKPAD
|
||||
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32
|
||||
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
|
||||
google_breakpad::MinidumpDescriptor(QFile::encodeName(dumpspath).toStdString()),
|
||||
/*FilterCallback*/ 0,
|
||||
DumpCallback,
|
||||
/*context*/ 0,
|
||||
true,
|
||||
-1
|
||||
);
|
||||
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
void FinishCatching() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
#if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
|
||||
|
||||
delete base::take(BreakpadExceptionHandler);
|
||||
|
||||
#endif // !Q_OS_MAC || MAC_USE_BREAKPAD
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
Status Start() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
ReportPath = cWorkingDir() + qsl("tdata/working");
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
FILE *f = nullptr;
|
||||
if (_wfopen_s(&f, ReportPath.toStdWString().c_str(), L"rb") != 0) {
|
||||
f = nullptr;
|
||||
} else {
|
||||
#else // !Q_OS_WIN
|
||||
if (FILE *f = fopen(QFile::encodeName(ReportPath).constData(), "rb")) {
|
||||
#endif // else for !Q_OS_WIN
|
||||
QByteArray lastdump;
|
||||
char buffer[256 * 1024] = { 0 };
|
||||
int32 read = fread(buffer, 1, 256 * 1024, f);
|
||||
if (read > 0) {
|
||||
lastdump.append(buffer, read);
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
Sandbox::SetLastCrashDump(lastdump);
|
||||
|
||||
LOG(("Opened '%1' for reading, the previous Telegram Desktop launch was not finished properly :( Crash log size: %2").arg(ReportPath).arg(lastdump.size()));
|
||||
|
||||
return LastCrashed;
|
||||
}
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
return Restart();
|
||||
}
|
||||
|
||||
Status Restart() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
if (ReportFile) {
|
||||
return Started;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (_wfopen_s(&ReportFile, ReportPath.toStdWString().c_str(), L"wb") != 0) {
|
||||
ReportFile = nullptr;
|
||||
}
|
||||
#else // Q_OS_WIN
|
||||
ReportFile = fopen(QFile::encodeName(ReportPath).constData(), "wb");
|
||||
#endif // else for Q_OS_WIN
|
||||
if (ReportFile) {
|
||||
#ifdef Q_OS_WIN
|
||||
ReportFileNo = _fileno(ReportFile);
|
||||
#else // Q_OS_WIN
|
||||
ReportFileNo = fileno(ReportFile);
|
||||
#endif // else for Q_OS_WIN
|
||||
if (SetSignalHandlers) {
|
||||
#ifndef Q_OS_WIN
|
||||
struct sigaction sigact;
|
||||
|
||||
sigact.sa_sigaction = SignalHandler;
|
||||
sigemptyset(&sigact.sa_mask);
|
||||
sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
|
||||
|
||||
sigaction(SIGABRT, &sigact, &SIG_def[SIGABRT]);
|
||||
sigaction(SIGSEGV, &sigact, &SIG_def[SIGSEGV]);
|
||||
sigaction(SIGILL, &sigact, &SIG_def[SIGILL]);
|
||||
sigaction(SIGFPE, &sigact, &SIG_def[SIGFPE]);
|
||||
sigaction(SIGBUS, &sigact, &SIG_def[SIGBUS]);
|
||||
sigaction(SIGSYS, &sigact, &SIG_def[SIGSYS]);
|
||||
#else // !Q_OS_WIN
|
||||
signal(SIGABRT, SignalHandler);
|
||||
signal(SIGSEGV, SignalHandler);
|
||||
signal(SIGILL, SignalHandler);
|
||||
signal(SIGFPE, SignalHandler);
|
||||
#endif // else for !Q_OS_WIN
|
||||
}
|
||||
|
||||
InstallOperatorNewHandler();
|
||||
|
||||
return Started;
|
||||
}
|
||||
|
||||
LOG(("FATAL: Could not open '%1' for writing!").arg(ReportPath));
|
||||
|
||||
return CantOpen;
|
||||
#else // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
return Started;
|
||||
#endif // else for !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
void Finish() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
FinishCatching();
|
||||
|
||||
if (ReportFile) {
|
||||
fclose(ReportFile);
|
||||
ReportFile = nullptr;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
_wunlink(ReportPath.toStdWString().c_str());
|
||||
#else // Q_OS_WIN
|
||||
unlink(ReportPath.toUtf8().constData());
|
||||
#endif // else for Q_OS_WIN
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
void SetAnnotation(const std::string &key, const QString &value) {
|
||||
static QMutex mutex;
|
||||
QMutexLocker lock(&mutex);
|
||||
|
||||
if (!value.trimmed().isEmpty()) {
|
||||
ProcessAnnotations[key] = value.toUtf8().constData();
|
||||
} else {
|
||||
ProcessAnnotations.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
void SetAnnotationRef(const std::string &key, const QString *valuePtr) {
|
||||
if (valuePtr) {
|
||||
ProcessAnnotationRefs[key] = valuePtr;
|
||||
} else {
|
||||
ProcessAnnotationRefs.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
dump::~dump() {
|
||||
if (ReportFile) {
|
||||
fflush(ReportFile);
|
||||
}
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, const char *str) {
|
||||
if (!ReportFile) return stream;
|
||||
|
||||
fwrite(str, 1, strlen(str), ReportFile);
|
||||
return stream;
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, const wchar_t *str) {
|
||||
if (!ReportFile) return stream;
|
||||
|
||||
for (int i = 0, l = wcslen(str); i < l; ++i) {
|
||||
if (str[i] >= 0 && str[i] < 128) {
|
||||
SafeWriteChar(char(str[i]));
|
||||
} else {
|
||||
SafeWriteChar('?');
|
||||
}
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, int num) {
|
||||
return SafeWriteNumber(stream, num);
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, unsigned int num) {
|
||||
return SafeWriteNumber(stream, num);
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, unsigned long num) {
|
||||
return SafeWriteNumber(stream, num);
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, unsigned long long num) {
|
||||
return SafeWriteNumber(stream, num);
|
||||
}
|
||||
|
||||
const dump &operator<<(const dump &stream, double num) {
|
||||
if (num < 0) {
|
||||
SafeWriteChar('-');
|
||||
num = -num;
|
||||
}
|
||||
SafeWriteNumber(stream, uint64(floor(num)));
|
||||
SafeWriteChar('.');
|
||||
num -= floor(num);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
num *= 10;
|
||||
int digit = int(floor(num));
|
||||
SafeWriteChar('0' + digit);
|
||||
num -= digit;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
#endif // TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
} // namespace CrashReports
|
||||
85
Telegram/SourceFiles/core/crash_reports.h
Normal file
85
Telegram/SourceFiles/core/crash_reports.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace CrashReports {
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
struct dump {
|
||||
~dump();
|
||||
};
|
||||
const dump &operator<<(const dump &stream, const char *str);
|
||||
const dump &operator<<(const dump &stream, const wchar_t *str);
|
||||
const dump &operator<<(const dump &stream, int num);
|
||||
const dump &operator<<(const dump &stream, unsigned int num);
|
||||
const dump &operator<<(const dump &stream, unsigned long num);
|
||||
const dump &operator<<(const dump &stream, unsigned long long num);
|
||||
const dump &operator<<(const dump &stream, double num);
|
||||
|
||||
#endif // TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
enum Status {
|
||||
CantOpen,
|
||||
LastCrashed,
|
||||
Started
|
||||
};
|
||||
Status Start();
|
||||
Status Restart(); // can be only CantOpen or Started
|
||||
void Finish();
|
||||
|
||||
void SetAnnotation(const std::string &key, const QString &value);
|
||||
inline void ClearAnnotation(const std::string &key) {
|
||||
SetAnnotation(key, QString());
|
||||
}
|
||||
|
||||
// Remembers value pointer and tries to add the value to the crash report.
|
||||
// Attention! You should call clearCrashAnnotationRef(key) before destroying value.
|
||||
void SetAnnotationRef(const std::string &key, const QString *valuePtr);
|
||||
inline void ClearAnnotationRef(const std::string &key) {
|
||||
SetAnnotationRef(key, nullptr);
|
||||
}
|
||||
|
||||
void StartCatching();
|
||||
void FinishCatching();
|
||||
|
||||
} // namespace CrashReports
|
||||
|
||||
namespace base {
|
||||
namespace assertion {
|
||||
|
||||
inline void log(const char *message, const char *file, int line) {
|
||||
const auto info = QStringLiteral("%1 %2:%3"
|
||||
).arg(message
|
||||
).arg(file
|
||||
).arg(line
|
||||
);
|
||||
const auto entry = QStringLiteral("Assertion Failed! ") + info;
|
||||
|
||||
#ifdef LOG
|
||||
LOG((entry));
|
||||
#endif // LOG
|
||||
|
||||
CrashReports::SetAnnotation("Assertion", info);
|
||||
}
|
||||
|
||||
} // namespace assertion
|
||||
} // namespace base
|
||||
@@ -23,10 +23,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "mainwindow.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "messenger.h"
|
||||
|
||||
bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &initialPath) {
|
||||
bool filedialogGetSaveFile(
|
||||
QString &file,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
const QString &initialPath) {
|
||||
QStringList files;
|
||||
QByteArray remoteContent;
|
||||
bool result = Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::WriteFile, initialPath);
|
||||
@@ -34,7 +37,12 @@ bool filedialogGetSaveFile(QString &file, const QString &caption, const QString
|
||||
return result;
|
||||
}
|
||||
|
||||
QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path, bool skipExistance, int fileTime) {
|
||||
QString filedialogDefaultName(
|
||||
const QString &prefix,
|
||||
const QString &extension,
|
||||
const QString &path,
|
||||
bool skipExistance,
|
||||
int fileTime) {
|
||||
auto directoryPath = path;
|
||||
if (directoryPath.isEmpty()) {
|
||||
if (cDialogLastPath().isEmpty()) {
|
||||
@@ -69,7 +77,10 @@ QString filedialogDefaultName(const QString &prefix, const QString &extension, c
|
||||
return name;
|
||||
}
|
||||
|
||||
QString filedialogNextFilename(const QString &name, const QString &cur, const QString &path) {
|
||||
QString filedialogNextFilename(
|
||||
const QString &name,
|
||||
const QString &cur,
|
||||
const QString &path) {
|
||||
QDir dir(path.isEmpty() ? cDialogLastPath() : path);
|
||||
int32 extIndex = name.lastIndexOf('.');
|
||||
QString prefix = name, extension;
|
||||
@@ -87,13 +98,13 @@ QString filedialogNextFilename(const QString &name, const QString &cur, const QS
|
||||
namespace File {
|
||||
|
||||
void OpenEmailLink(const QString &email) {
|
||||
base::TaskQueue::Main().Put([email] {
|
||||
crl::on_main([=] {
|
||||
Platform::File::UnsafeOpenEmailLink(email);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenWith(const QString &filepath, QPoint menuPosition) {
|
||||
base::TaskQueue::Main().Put([filepath, menuPosition] {
|
||||
crl::on_main([=] {
|
||||
if (!Platform::File::UnsafeShowOpenWithDropdown(filepath, menuPosition)) {
|
||||
if (!Platform::File::UnsafeShowOpenWith(filepath)) {
|
||||
Platform::File::UnsafeLaunch(filepath);
|
||||
@@ -103,13 +114,13 @@ void OpenWith(const QString &filepath, QPoint menuPosition) {
|
||||
}
|
||||
|
||||
void Launch(const QString &filepath) {
|
||||
base::TaskQueue::Main().Put([filepath] {
|
||||
crl::on_main([=] {
|
||||
Platform::File::UnsafeLaunch(filepath);
|
||||
});
|
||||
}
|
||||
|
||||
void ShowInFolder(const QString &filepath) {
|
||||
base::TaskQueue::Main().Put([filepath] {
|
||||
crl::on_main([=] {
|
||||
Platform::File::UnsafeShowInFolder(filepath);
|
||||
});
|
||||
}
|
||||
@@ -130,19 +141,30 @@ void UnsafeLaunchDefault(const QString &filepath) {
|
||||
|
||||
namespace FileDialog {
|
||||
|
||||
void GetOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([caption, filter, callback = std::move(callback), failed = std::move(failed)] {
|
||||
void GetOpenPath(
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
base::lambda<void(OpenResult &&result)> callback,
|
||||
base::lambda<void()> failed) {
|
||||
crl::on_main([=] {
|
||||
auto files = QStringList();
|
||||
auto remoteContent = QByteArray();
|
||||
if (Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFile)
|
||||
&& ((!files.isEmpty() && !files[0].isEmpty()) || !remoteContent.isEmpty())) {
|
||||
const auto success = Platform::FileDialog::Get(
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
FileDialog::internal::Type::ReadFile);
|
||||
if (success
|
||||
&& ((!files.isEmpty() && !files[0].isEmpty())
|
||||
|| !remoteContent.isEmpty())) {
|
||||
if (callback) {
|
||||
auto result = OpenResult();
|
||||
if (!files.isEmpty() && !files[0].isEmpty()) {
|
||||
result.paths.push_back(files[0]);
|
||||
}
|
||||
result.remoteContent = remoteContent;
|
||||
callback(result);
|
||||
callback(std::move(result));
|
||||
}
|
||||
} else if (failed) {
|
||||
failed();
|
||||
@@ -150,17 +172,26 @@ void GetOpenPath(const QString &caption, const QString &filter, base::lambda<voi
|
||||
});
|
||||
}
|
||||
|
||||
void GetOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([caption, filter, callback = std::move(callback), failed = std::move(failed)] {
|
||||
void GetOpenPaths(
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
base::lambda<void(OpenResult &&result)> callback,
|
||||
base::lambda<void()> failed) {
|
||||
crl::on_main([=] {
|
||||
auto files = QStringList();
|
||||
auto remoteContent = QByteArray();
|
||||
if (Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFiles)
|
||||
&& (!files.isEmpty() || !remoteContent.isEmpty())) {
|
||||
const auto success = Platform::FileDialog::Get(
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
FileDialog::internal::Type::ReadFiles);
|
||||
if (success && (!files.isEmpty() || !remoteContent.isEmpty())) {
|
||||
if (callback) {
|
||||
auto result = OpenResult();
|
||||
result.paths = files;
|
||||
result.remoteContent = remoteContent;
|
||||
callback(result);
|
||||
callback(std::move(result));
|
||||
}
|
||||
} else if (failed) {
|
||||
failed();
|
||||
@@ -168,12 +199,17 @@ void GetOpenPaths(const QString &caption, const QString &filter, base::lambda<vo
|
||||
});
|
||||
}
|
||||
|
||||
void GetWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([caption, filter, initialPath, callback = std::move(callback), failed = std::move(failed)] {
|
||||
void GetWritePath(
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
const QString &initialPath,
|
||||
base::lambda<void(QString &&result)> callback,
|
||||
base::lambda<void()> failed) {
|
||||
crl::on_main([=] {
|
||||
auto file = QString();
|
||||
if (filedialogGetSaveFile(file, caption, filter, initialPath)) {
|
||||
if (callback) {
|
||||
callback(file);
|
||||
callback(std::move(file));
|
||||
}
|
||||
} else if (failed) {
|
||||
failed();
|
||||
@@ -181,14 +217,24 @@ void GetWritePath(const QString &caption, const QString &filter, const QString &
|
||||
});
|
||||
}
|
||||
|
||||
void GetFolder(const QString &caption, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([caption, initialPath, callback = std::move(callback), failed = std::move(failed)] {
|
||||
void GetFolder(
|
||||
const QString &caption,
|
||||
const QString &initialPath,
|
||||
base::lambda<void(QString &&result)> callback,
|
||||
base::lambda<void()> failed) {
|
||||
crl::on_main([=] {
|
||||
auto files = QStringList();
|
||||
auto remoteContent = QByteArray();
|
||||
if (Platform::FileDialog::Get(files, remoteContent, caption, QString(), FileDialog::internal::Type::ReadFolder, initialPath)
|
||||
&& !files.isEmpty() && !files[0].isEmpty()) {
|
||||
const auto success = Platform::FileDialog::Get(
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
QString(),
|
||||
FileDialog::internal::Type::ReadFolder,
|
||||
initialPath);
|
||||
if (success && !files.isEmpty() && !files[0].isEmpty()) {
|
||||
if (callback) {
|
||||
callback(files[0]);
|
||||
callback(std::move(files[0]));
|
||||
}
|
||||
} else if (failed) {
|
||||
failed();
|
||||
|
||||
@@ -23,10 +23,22 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "base/observer.h"
|
||||
|
||||
// legacy
|
||||
bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &initialPath);
|
||||
bool filedialogGetSaveFile(
|
||||
QString &file,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
const QString &initialPath);
|
||||
|
||||
QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path = QString(), bool skipExistance = false, int fileTime = 0);
|
||||
QString filedialogNextFilename(const QString &name, const QString &cur, const QString &path = QString());
|
||||
QString filedialogDefaultName(
|
||||
const QString &prefix,
|
||||
const QString &extension,
|
||||
const QString &path = QString(),
|
||||
bool skipExistance = false,
|
||||
int fileTime = 0);
|
||||
QString filedialogNextFilename(
|
||||
const QString &name,
|
||||
const QString &cur,
|
||||
const QString &path = QString());
|
||||
|
||||
namespace File {
|
||||
|
||||
@@ -54,10 +66,27 @@ struct OpenResult {
|
||||
QStringList paths;
|
||||
QByteArray remoteContent;
|
||||
};
|
||||
void GetOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetFolder(const QString &caption, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetOpenPath(
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
base::lambda<void(OpenResult &&result)> callback,
|
||||
base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetOpenPaths(
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
base::lambda<void(OpenResult &&result)> callback,
|
||||
base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetWritePath(
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
const QString &initialPath,
|
||||
base::lambda<void(QString &&result)> callback,
|
||||
base::lambda<void()> failed = base::lambda<void()>());
|
||||
void GetFolder(
|
||||
const QString &caption,
|
||||
const QString &initialPath,
|
||||
base::lambda<void(QString &&result)> callback,
|
||||
base::lambda<void()> failed = base::lambda<void()>());
|
||||
|
||||
QString AllFilesFilter();
|
||||
|
||||
@@ -72,7 +101,13 @@ enum class Type {
|
||||
|
||||
void InitLastPathDefault();
|
||||
|
||||
bool GetDefault(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile);
|
||||
bool GetDefault(
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
::FileDialog::internal::Type type,
|
||||
QString startFile);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace FileDialog
|
||||
|
||||
246
Telegram/SourceFiles/core/launcher.cpp
Normal file
246
Telegram/SourceFiles/core/launcher.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "core/launcher.h"
|
||||
|
||||
#include "platform/platform_launcher.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/main_queue_processor.h"
|
||||
#include "application.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) {
|
||||
return std::make_unique<Platform::Launcher>(argc, argv);
|
||||
}
|
||||
|
||||
Launcher::Launcher(int argc, char *argv[])
|
||||
: _argc(argc)
|
||||
, _argv(argv) {
|
||||
}
|
||||
|
||||
void Launcher::init() {
|
||||
_arguments = readArguments(_argc, _argv);
|
||||
|
||||
prepareSettings();
|
||||
|
||||
QCoreApplication::setApplicationName(qsl("TelegramDesktop"));
|
||||
|
||||
#ifndef OS_MAC_OLD
|
||||
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
|
||||
#endif // OS_MAC_OLD
|
||||
|
||||
initHook();
|
||||
}
|
||||
|
||||
int Launcher::exec() {
|
||||
init();
|
||||
|
||||
if (cLaunchMode() == LaunchModeFixPrevious) {
|
||||
return psFixPrevious();
|
||||
} else if (cLaunchMode() == LaunchModeCleanup) {
|
||||
return psCleanup();
|
||||
}
|
||||
|
||||
// both are finished in Application::closeApplication
|
||||
Logs::start(this); // must be started before Platform is started
|
||||
Platform::start(); // must be started before QApplication is created
|
||||
|
||||
auto result = executeApplication();
|
||||
|
||||
DEBUG_LOG(("Telegram finished, result: %1").arg(result));
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
|
||||
if (cRestartingUpdate()) {
|
||||
DEBUG_LOG(("Application Info: executing updater to install update..."));
|
||||
if (!launchUpdater(UpdaterLaunch::PerformUpdate)) {
|
||||
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
|
||||
}
|
||||
} else
|
||||
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
|
||||
if (cRestarting()) {
|
||||
DEBUG_LOG(("Application Info: executing Telegram, because of restart..."));
|
||||
launchUpdater(UpdaterLaunch::JustRelaunch);
|
||||
}
|
||||
|
||||
CrashReports::Finish();
|
||||
Platform::finish();
|
||||
Logs::finish();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList Launcher::readArguments(int argc, char *argv[]) const {
|
||||
Expects(argc >= 0);
|
||||
|
||||
if (const auto native = readArgumentsHook(argc, argv)) {
|
||||
return *native;
|
||||
}
|
||||
|
||||
auto result = QStringList();
|
||||
result.reserve(argc);
|
||||
for (auto i = 0; i != argc; ++i) {
|
||||
result.push_back(fromUtf8Safe(argv[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Launcher::argumentsString() const {
|
||||
return _arguments.join(' ');
|
||||
}
|
||||
|
||||
void Launcher::prepareSettings() {
|
||||
#ifdef Q_OS_MAC
|
||||
#ifndef OS_MAC_OLD
|
||||
if (QSysInfo::macVersion() >= QSysInfo::MV_10_11) {
|
||||
gIsElCapitan = true;
|
||||
}
|
||||
#else // OS_MAC_OLD
|
||||
if (QSysInfo::macVersion() < QSysInfo::MV_10_7) {
|
||||
gIsSnowLeopard = true;
|
||||
}
|
||||
#endif // OS_MAC_OLD
|
||||
#endif // Q_OS_MAC
|
||||
|
||||
switch (cPlatform()) {
|
||||
case dbipWindows:
|
||||
gUpdateURL = QUrl(qsl("http://tdesktop.com/win/tupdates/current"));
|
||||
#ifndef OS_WIN_STORE
|
||||
gPlatformString = qsl("Windows");
|
||||
#else // OS_WIN_STORE
|
||||
gPlatformString = qsl("WinStore");
|
||||
#endif // OS_WIN_STORE
|
||||
break;
|
||||
case dbipMac:
|
||||
gUpdateURL = QUrl(qsl("http://tdesktop.com/mac/tupdates/current"));
|
||||
#ifndef OS_MAC_STORE
|
||||
gPlatformString = qsl("MacOS");
|
||||
#else // OS_MAC_STORE
|
||||
gPlatformString = qsl("MacAppStore");
|
||||
#endif // OS_MAC_STORE
|
||||
break;
|
||||
case dbipMacOld:
|
||||
gUpdateURL = QUrl(qsl("http://tdesktop.com/mac32/tupdates/current"));
|
||||
gPlatformString = qsl("MacOSold");
|
||||
break;
|
||||
case dbipLinux64:
|
||||
gUpdateURL = QUrl(qsl("http://tdesktop.com/linux/tupdates/current"));
|
||||
gPlatformString = qsl("Linux64bit");
|
||||
break;
|
||||
case dbipLinux32:
|
||||
gUpdateURL = QUrl(qsl("http://tdesktop.com/linux32/tupdates/current"));
|
||||
gPlatformString = qsl("Linux32bit");
|
||||
break;
|
||||
}
|
||||
|
||||
auto path = Platform::CurrentExecutablePath(_argc, _argv);
|
||||
LOG(("Executable path before check: %1").arg(path));
|
||||
if (!path.isEmpty()) {
|
||||
auto info = QFileInfo(path);
|
||||
if (info.isSymLink()) {
|
||||
info = info.symLinkTarget();
|
||||
}
|
||||
if (info.exists()) {
|
||||
gExeDir = info.absoluteDir().absolutePath() + '/';
|
||||
gExeName = info.fileName();
|
||||
}
|
||||
}
|
||||
if (cExeName().isEmpty()) {
|
||||
LOG(("WARNING: Could not compute executable path, some features will be disabled."));
|
||||
}
|
||||
|
||||
processArguments();
|
||||
}
|
||||
|
||||
void Launcher::processArguments() {
|
||||
enum class KeyFormat {
|
||||
NoValues,
|
||||
OneValue,
|
||||
AllLeftValues,
|
||||
};
|
||||
auto parseMap = std::map<QByteArray, KeyFormat> {
|
||||
{ "-testmode" , KeyFormat::NoValues },
|
||||
{ "-debug" , KeyFormat::NoValues },
|
||||
{ "-many" , KeyFormat::NoValues },
|
||||
{ "-key" , KeyFormat::OneValue },
|
||||
{ "-autostart" , KeyFormat::NoValues },
|
||||
{ "-fixprevious", KeyFormat::NoValues },
|
||||
{ "-cleanup" , KeyFormat::NoValues },
|
||||
{ "-noupdate" , KeyFormat::NoValues },
|
||||
{ "-tosettings" , KeyFormat::NoValues },
|
||||
{ "-startintray", KeyFormat::NoValues },
|
||||
{ "-sendpath" , KeyFormat::AllLeftValues },
|
||||
{ "-workdir" , KeyFormat::OneValue },
|
||||
{ "--" , KeyFormat::OneValue },
|
||||
};
|
||||
auto parseResult = QMap<QByteArray, QStringList>();
|
||||
auto parsingKey = QByteArray();
|
||||
auto parsingFormat = KeyFormat::NoValues;
|
||||
for (const auto &argument : _arguments) {
|
||||
switch (parsingFormat) {
|
||||
case KeyFormat::OneValue: {
|
||||
parseResult[parsingKey] = QStringList(argument.mid(0, 8192));
|
||||
parsingFormat = KeyFormat::NoValues;
|
||||
} break;
|
||||
case KeyFormat::AllLeftValues: {
|
||||
parseResult[parsingKey].push_back(argument.mid(0, 8192));
|
||||
} break;
|
||||
case KeyFormat::NoValues: {
|
||||
parsingKey = argument.toLatin1();
|
||||
auto it = parseMap.find(parsingKey);
|
||||
if (it != parseMap.end()) {
|
||||
parsingFormat = it->second;
|
||||
parseResult[parsingKey] = QStringList();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
gTestMode = parseResult.contains("-testmode");
|
||||
gDebug = parseResult.contains("-debug");
|
||||
gManyInstance = parseResult.contains("-many");
|
||||
gKeyFile = parseResult.value("-key", QStringList()).join(QString());
|
||||
gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart
|
||||
: parseResult.contains("-fixprevious") ? LaunchModeFixPrevious
|
||||
: parseResult.contains("-cleanup") ? LaunchModeCleanup
|
||||
: LaunchModeNormal;
|
||||
gNoStartUpdate = parseResult.contains("-noupdate");
|
||||
gStartToSettings = parseResult.contains("-tosettings");
|
||||
gStartInTray = parseResult.contains("-startintray");
|
||||
gSendPaths = parseResult.value("-sendpath", QStringList());
|
||||
gWorkingDir = parseResult.value("-workdir", QStringList()).join(QString());
|
||||
if (!gWorkingDir.isEmpty()) {
|
||||
if (QDir().exists(gWorkingDir)) {
|
||||
_customWorkingDir = true;
|
||||
} else {
|
||||
gWorkingDir = QString();
|
||||
}
|
||||
}
|
||||
gStartUrl = parseResult.value("--", QStringList()).join(QString());
|
||||
}
|
||||
|
||||
int Launcher::executeApplication() {
|
||||
MainQueueProcessor processor;
|
||||
Application app(this, _argc, _argv);
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
71
Telegram/SourceFiles/core/launcher.h
Normal file
71
Telegram/SourceFiles/core/launcher.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Launcher {
|
||||
public:
|
||||
Launcher(int argc, char *argv[]);
|
||||
|
||||
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
|
||||
|
||||
int exec();
|
||||
|
||||
QString argumentsString() const;
|
||||
bool customWorkingDir() const {
|
||||
return _customWorkingDir;
|
||||
}
|
||||
|
||||
protected:
|
||||
enum class UpdaterLaunch {
|
||||
PerformUpdate,
|
||||
JustRelaunch,
|
||||
};
|
||||
|
||||
private:
|
||||
void prepareSettings();
|
||||
void processArguments();
|
||||
|
||||
QStringList readArguments(int argc, char *argv[]) const;
|
||||
virtual base::optional<QStringList> readArgumentsHook(
|
||||
int argc,
|
||||
char *argv[]) const {
|
||||
return base::none;
|
||||
}
|
||||
|
||||
void init();
|
||||
virtual void initHook() {
|
||||
}
|
||||
|
||||
virtual bool launchUpdater(UpdaterLaunch action) = 0;
|
||||
|
||||
int executeApplication();
|
||||
|
||||
int _argc;
|
||||
char **_argv;
|
||||
QStringList _arguments;
|
||||
|
||||
bool _customWorkingDir = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
96
Telegram/SourceFiles/core/main_queue_processor.cpp
Normal file
96
Telegram/SourceFiles/core/main_queue_processor.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "core/main_queue_processor.h"
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
QMutex ProcessorMutex;
|
||||
MainQueueProcessor *ProcessorInstance/* = nullptr*/;
|
||||
|
||||
constexpr auto kProcessorEvent = QEvent::Type(QEvent::User + 1);
|
||||
static_assert(kProcessorEvent < QEvent::MaxUser);
|
||||
|
||||
class ProcessorEvent : public QEvent {
|
||||
public:
|
||||
ProcessorEvent(void (*callable)(void*), void *argument);
|
||||
|
||||
void process();
|
||||
|
||||
private:
|
||||
void (*_callable)(void*) = nullptr;
|
||||
void *_argument = nullptr;
|
||||
|
||||
};
|
||||
|
||||
ProcessorEvent::ProcessorEvent(void (*callable)(void*), void *argument)
|
||||
: QEvent(kProcessorEvent)
|
||||
, _callable(callable)
|
||||
, _argument(argument) {
|
||||
}
|
||||
|
||||
void ProcessorEvent::process() {
|
||||
_callable(_argument);
|
||||
}
|
||||
|
||||
void ProcessMainQueue(void (*callable)(void*), void *argument) {
|
||||
QMutexLocker lock(&ProcessorMutex);
|
||||
|
||||
if (ProcessorInstance) {
|
||||
const auto event = new ProcessorEvent(callable, argument);
|
||||
QCoreApplication::postEvent(ProcessorInstance, event);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MainQueueProcessor::MainQueueProcessor() {
|
||||
acquire();
|
||||
crl::init_main_queue(ProcessMainQueue);
|
||||
}
|
||||
|
||||
bool MainQueueProcessor::event(QEvent *event) {
|
||||
if (event->type() == kProcessorEvent) {
|
||||
static_cast<ProcessorEvent*>(event)->process();
|
||||
return true;
|
||||
}
|
||||
return QObject::event(event);
|
||||
}
|
||||
|
||||
void MainQueueProcessor::acquire() {
|
||||
Expects(ProcessorInstance == nullptr);
|
||||
|
||||
QMutexLocker lock(&ProcessorMutex);
|
||||
ProcessorInstance = this;
|
||||
}
|
||||
|
||||
void MainQueueProcessor::release() {
|
||||
Expects(ProcessorInstance == this);
|
||||
|
||||
QMutexLocker lock(&ProcessorMutex);
|
||||
ProcessorInstance = nullptr;
|
||||
}
|
||||
|
||||
MainQueueProcessor::~MainQueueProcessor() {
|
||||
release();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
40
Telegram/SourceFiles/core/main_queue_processor.h
Normal file
40
Telegram/SourceFiles/core/main_queue_processor.h
Normal file
@@ -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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
|
||||
class MainQueueProcessor : public QObject {
|
||||
public:
|
||||
MainQueueProcessor();
|
||||
|
||||
~MainQueueProcessor();
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
private:
|
||||
void acquire();
|
||||
void release();
|
||||
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -545,11 +545,6 @@ static int32 QuarterArcLength = (FullArcLength / 4);
|
||||
static int32 MinArcLength = (FullArcLength / 360);
|
||||
static int32 AlmostFullArcLength = (FullArcLength - MinArcLength);
|
||||
|
||||
template <typename T, typename... Args>
|
||||
inline QSharedPointer<T> MakeShared(Args&&... args) {
|
||||
return QSharedPointer<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
// This pointer is used for global non-POD variables that are allocated
|
||||
// on demand by createIfNull(lambda) and are never automatically freed.
|
||||
template <typename T>
|
||||
|
||||
@@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
#define BETA_VERSION_MACRO (0ULL)
|
||||
|
||||
constexpr int AppVersion = 1002000;
|
||||
constexpr str_const AppVersionStr = "1.2";
|
||||
constexpr bool AppAlphaVersion = false;
|
||||
constexpr int AppVersion = 1002007;
|
||||
constexpr str_const AppVersionStr = "1.2.7";
|
||||
constexpr bool AppAlphaVersion = true;
|
||||
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;
|
||||
|
||||
@@ -224,7 +224,10 @@ QString documentSaveFilename(const DocumentData *data, bool forceSavingAs = fals
|
||||
return saveFileName(caption, filter, prefix, name, forceSavingAs, dir);
|
||||
}
|
||||
|
||||
void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context, ActionOnLoad action) {
|
||||
void DocumentOpenClickHandler::doOpen(
|
||||
not_null<DocumentData*> data,
|
||||
HistoryItem *context,
|
||||
ActionOnLoad action) {
|
||||
if (!data->date) return;
|
||||
|
||||
auto msgId = context ? context->fullId() : FullMsgId();
|
||||
@@ -329,23 +332,20 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
|
||||
}
|
||||
|
||||
void DocumentOpenClickHandler::onClickImpl() const {
|
||||
const auto item = App::hoveredLinkItem()
|
||||
? App::hoveredLinkItem()
|
||||
: (App::contextItem() ? App::contextItem() : nullptr);
|
||||
const auto action = document()->isVoiceMessage()
|
||||
const auto data = document();
|
||||
const auto action = data->isVoiceMessage()
|
||||
? ActionOnLoadNone
|
||||
: ActionOnLoadOpen;
|
||||
doOpen(document(), item, action);
|
||||
doOpen(data, getActionItem(), action);
|
||||
}
|
||||
|
||||
void GifOpenClickHandler::onClickImpl() const {
|
||||
const auto item = App::hoveredLinkItem()
|
||||
? App::hoveredLinkItem()
|
||||
: (App::contextItem() ? App::contextItem() : nullptr);
|
||||
doOpen(document(), item, ActionOnLoadPlayInline);
|
||||
doOpen(document(), getActionItem(), ActionOnLoadPlayInline);
|
||||
}
|
||||
|
||||
void DocumentSaveClickHandler::doSave(DocumentData *data, bool forceSavingAs) {
|
||||
void DocumentSaveClickHandler::doSave(
|
||||
not_null<DocumentData*> data,
|
||||
bool forceSavingAs) {
|
||||
if (!data->date) return;
|
||||
|
||||
auto filepath = data->filepath(DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs);
|
||||
@@ -369,17 +369,13 @@ void DocumentSaveClickHandler::onClickImpl() const {
|
||||
}
|
||||
|
||||
void DocumentCancelClickHandler::onClickImpl() const {
|
||||
auto data = document();
|
||||
const auto data = document();
|
||||
if (!data->date) return;
|
||||
|
||||
if (data->uploading()) {
|
||||
if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) {
|
||||
if (auto media = item->getMedia()) {
|
||||
if (media->getDocument() == data) {
|
||||
App::contextItem(item);
|
||||
App::main()->cancelUploadLayer();
|
||||
}
|
||||
}
|
||||
if (const auto item = App::histItemById(context())) {
|
||||
App::contextItem(item);
|
||||
App::main()->cancelUploadLayer();
|
||||
}
|
||||
} else {
|
||||
data->cancel();
|
||||
@@ -672,12 +668,19 @@ QString DocumentData::loadingFilePath() const {
|
||||
}
|
||||
|
||||
bool DocumentData::displayLoading() const {
|
||||
return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : uploading();
|
||||
return loading()
|
||||
? (!_loader->loadingLocal() || !_loader->autoLoading())
|
||||
: (uploading() && !waitingForAlbum());
|
||||
}
|
||||
|
||||
float64 DocumentData::progress() const {
|
||||
if (uploading()) {
|
||||
return snap((size > 0) ? float64(uploadOffset) / size : 0., 0., 1.);
|
||||
if (uploadingData->size > 0) {
|
||||
const auto result = float64(uploadingData->offset)
|
||||
/ uploadingData->size;
|
||||
return snap(result, 0., 1.);
|
||||
}
|
||||
return 0.;
|
||||
}
|
||||
return loading() ? _loader->currentProgress() : (loaded() ? 1. : 0.);
|
||||
}
|
||||
@@ -687,7 +690,17 @@ int32 DocumentData::loadOffset() const {
|
||||
}
|
||||
|
||||
bool DocumentData::uploading() const {
|
||||
return status == FileUploading;
|
||||
return (uploadingData != nullptr);
|
||||
}
|
||||
|
||||
void DocumentData::setWaitingForAlbum() {
|
||||
if (uploading()) {
|
||||
uploadingData->waitingForAlbum = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool DocumentData::waitingForAlbum() const {
|
||||
return uploading() && uploadingData->waitingForAlbum;
|
||||
}
|
||||
|
||||
void DocumentData::save(const QString &toFile, ActionOnLoad action, const FullMsgId &actionMsgId, LoadFromCloudSetting fromCloud, bool autoLoading) {
|
||||
|
||||
@@ -125,6 +125,9 @@ public:
|
||||
int32 loadOffset() const;
|
||||
bool uploading() const;
|
||||
|
||||
void setWaitingForAlbum();
|
||||
bool waitingForAlbum() const;
|
||||
|
||||
QByteArray data() const;
|
||||
const FileLocation &location(bool check = false) const;
|
||||
void setLocation(const FileLocation &loc);
|
||||
@@ -241,7 +244,8 @@ public:
|
||||
int32 size = 0;
|
||||
|
||||
FileStatus status = FileReady;
|
||||
int32 uploadOffset = 0;
|
||||
|
||||
std::unique_ptr<Data::UploadState> uploadingData;
|
||||
|
||||
int32 md5[8];
|
||||
|
||||
@@ -309,17 +313,20 @@ private:
|
||||
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit);
|
||||
QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);
|
||||
|
||||
class DocumentClickHandler : public LeftButtonClickHandler {
|
||||
class DocumentClickHandler : public FileClickHandler {
|
||||
public:
|
||||
DocumentClickHandler(DocumentData *document)
|
||||
: _document(document) {
|
||||
DocumentClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context = FullMsgId())
|
||||
: FileClickHandler(context)
|
||||
, _document(document) {
|
||||
}
|
||||
DocumentData *document() const {
|
||||
not_null<DocumentData*> document() const {
|
||||
return _document;
|
||||
}
|
||||
|
||||
private:
|
||||
DocumentData *_document;
|
||||
not_null<DocumentData*> _document;
|
||||
|
||||
};
|
||||
|
||||
@@ -327,7 +334,7 @@ class DocumentSaveClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
using DocumentClickHandler::DocumentClickHandler;
|
||||
static void doSave(
|
||||
DocumentData *document,
|
||||
not_null<DocumentData*> document,
|
||||
bool forceSavingAs = false);
|
||||
|
||||
protected:
|
||||
@@ -339,7 +346,7 @@ class DocumentOpenClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
using DocumentClickHandler::DocumentClickHandler;
|
||||
static void doOpen(
|
||||
DocumentData *document,
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *context,
|
||||
ActionOnLoad action = ActionOnLoadOpen);
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "mainwindow.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/text_options.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -100,7 +101,7 @@ void PeerClickHandler::onClick(Qt::MouseButton button) const {
|
||||
PeerData::PeerData(const PeerId &id)
|
||||
: id(id)
|
||||
, _userpicEmpty(createEmptyUserpic()) {
|
||||
nameText.setText(st::msgNameStyle, QString(), _textNameOptions);
|
||||
nameText.setText(st::msgNameStyle, QString(), Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
void PeerData::updateNameDelayed(
|
||||
@@ -124,7 +125,7 @@ void PeerData::updateNameDelayed(
|
||||
|
||||
++nameVersion;
|
||||
name = newName;
|
||||
nameText.setText(st::msgNameStyle, name, _textNameOptions);
|
||||
nameText.setText(st::msgNameStyle, name, Ui::NameTextOptions());
|
||||
refreshEmptyUserpic();
|
||||
|
||||
Notify::PeerUpdate update(this);
|
||||
@@ -164,7 +165,7 @@ void PeerData::refreshEmptyUserpic() const {
|
||||
}
|
||||
|
||||
ClickHandlerPtr PeerData::createOpenLink() {
|
||||
return MakeShared<PeerClickHandler>(this);
|
||||
return std::make_shared<PeerClickHandler>(this);
|
||||
}
|
||||
|
||||
void PeerData::setUserpic(
|
||||
@@ -277,12 +278,7 @@ void PeerData::updateUserpic(
|
||||
const auto size = kUserpicSize;
|
||||
const auto loc = StorageImageLocation::FromMTP(size, size, location);
|
||||
const auto photo = loc.isNull() ? ImagePtr() : ImagePtr(loc);
|
||||
if (_userpicPhotoId != photoId
|
||||
|| _userpic.v() != photo.v()
|
||||
|| _userpicLocation != loc) {
|
||||
setUserpic(photoId, loc, photo);
|
||||
Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged);
|
||||
}
|
||||
setUserpicChecked(photoId, loc, photo);
|
||||
}
|
||||
|
||||
void PeerData::clearUserpic() {
|
||||
@@ -300,6 +296,19 @@ void PeerData::clearUserpic() {
|
||||
}
|
||||
return ImagePtr();
|
||||
}();
|
||||
setUserpicChecked(photoId, loc, photo);
|
||||
}
|
||||
|
||||
void PeerData::setUserpicChecked(
|
||||
PhotoId photoId,
|
||||
const StorageImageLocation &location,
|
||||
ImagePtr userpic) {
|
||||
if (_userpicPhotoId != photoId
|
||||
|| _userpic.v() != userpic.v()
|
||||
|| _userpicLocation != location) {
|
||||
setUserpic(photoId, location, userpic);
|
||||
Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerData::fillNames() {
|
||||
@@ -343,7 +352,10 @@ PeerData::~PeerData() = default;
|
||||
|
||||
const Text &BotCommand::descriptionText() const {
|
||||
if (_descriptionText.isEmpty() && !_description.isEmpty()) {
|
||||
_descriptionText.setText(st::defaultTextStyle, _description, _textNameOptions);
|
||||
_descriptionText.setText(
|
||||
st::defaultTextStyle,
|
||||
_description,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
return _descriptionText;
|
||||
}
|
||||
@@ -483,7 +495,10 @@ void UserData::setBotInfo(const MTPBotInfo &info) {
|
||||
void UserData::setNameOrPhone(const QString &newNameOrPhone) {
|
||||
if (nameOrPhone != newNameOrPhone) {
|
||||
nameOrPhone = newNameOrPhone;
|
||||
phoneText.setText(st::msgNameStyle, nameOrPhone, _textNameOptions);
|
||||
phoneText.setText(
|
||||
st::msgNameStyle,
|
||||
nameOrPhone,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,16 +583,18 @@ void ChatData::setInviteLink(const QString &newInviteLink) {
|
||||
ChannelData::ChannelData(const PeerId &id)
|
||||
: PeerData(id)
|
||||
, inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))) {
|
||||
Data::PeerFlagValue(this, MTPDchannel::Flag::f_megagroup)
|
||||
| rpl::start_with_next([this](bool megagroup) {
|
||||
if (megagroup) {
|
||||
if (!mgInfo) {
|
||||
mgInfo = std::make_unique<MegagroupInfo>();
|
||||
}
|
||||
} else if (mgInfo) {
|
||||
mgInfo = nullptr;
|
||||
Data::PeerFlagValue(
|
||||
this,
|
||||
MTPDchannel::Flag::f_megagroup
|
||||
) | rpl::start_with_next([this](bool megagroup) {
|
||||
if (megagroup) {
|
||||
if (!mgInfo) {
|
||||
mgInfo = std::make_unique<MegagroupInfo>();
|
||||
}
|
||||
}, _lifetime);
|
||||
} else if (mgInfo) {
|
||||
mgInfo = nullptr;
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void ChannelData::setPhoto(const MTPChatPhoto &photo) {
|
||||
@@ -784,9 +801,12 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChannelBann
|
||||
}
|
||||
Data::ChannelAdminChanges(this).feed(peerToUser(user->id), false);
|
||||
} else {
|
||||
if (isKicked && membersCount() > 1) {
|
||||
setMembersCount(membersCount() - 1);
|
||||
flags |= Notify::PeerUpdate::Flag::MembersChanged;
|
||||
if (isKicked) {
|
||||
if (membersCount() > 1) {
|
||||
setMembersCount(membersCount() - 1);
|
||||
flags |= Notify::PeerUpdate::Flag::MembersChanged;
|
||||
}
|
||||
setKickedCount(kickedCount() + 1);
|
||||
}
|
||||
}
|
||||
Notify::peerUpdatedDelayed(this, flags);
|
||||
|
||||
@@ -238,6 +238,11 @@ private:
|
||||
std::unique_ptr<Ui::EmptyUserpic> createEmptyUserpic() const;
|
||||
void refreshEmptyUserpic() const;
|
||||
|
||||
void setUserpicChecked(
|
||||
PhotoId photoId,
|
||||
const StorageImageLocation &location,
|
||||
ImagePtr userpic);
|
||||
|
||||
static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL);
|
||||
|
||||
ImagePtr _userpic;
|
||||
|
||||
@@ -20,7 +20,67 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "data/data_peer_values.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMinOnlineChangeTimeout = TimeMs(1000);
|
||||
constexpr auto kMaxOnlineChangeTimeout = 86400 * TimeMs(1000);
|
||||
|
||||
int OnlinePhraseChangeInSeconds(TimeId online, TimeId now) {
|
||||
if (online <= 0) {
|
||||
if (-online > now) {
|
||||
return (-online - now);
|
||||
}
|
||||
return std::numeric_limits<int32>::max();
|
||||
}
|
||||
if (online > now) {
|
||||
return online - now;
|
||||
}
|
||||
const auto minutes = (now - online) / 60;
|
||||
if (minutes < 60) {
|
||||
return (minutes + 1) * 60 - (now - online);
|
||||
}
|
||||
const auto hours = (now - online) / 3600;
|
||||
if (hours < 12) {
|
||||
return (hours + 1) * 3600 - (now - online);
|
||||
}
|
||||
const auto nowFull = ::date(now);
|
||||
const auto tomorrow = QDateTime(nowFull.date().addDays(1));
|
||||
return static_cast<int32>(nowFull.secsTo(tomorrow));
|
||||
}
|
||||
|
||||
base::optional<QString> OnlineTextSpecial(not_null<UserData*> user) {
|
||||
if (isNotificationsUser(user->id)) {
|
||||
return lang(lng_status_service_notifications);
|
||||
} else if (user->botInfo) {
|
||||
return lang(lng_status_bot);
|
||||
} else if (isServiceUser(user->id)) {
|
||||
return lang(lng_status_support);
|
||||
}
|
||||
return base::none;
|
||||
}
|
||||
|
||||
base::optional<QString> OnlineTextCommon(TimeId online, TimeId now) {
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
case -1: return lang(lng_status_offline);
|
||||
case -2: return lang(lng_status_recently);
|
||||
case -3: return lang(lng_status_last_week);
|
||||
case -4: return lang(lng_status_last_month);
|
||||
}
|
||||
return (-online > now)
|
||||
? lang(lng_status_online)
|
||||
: lang(lng_status_recently);
|
||||
} else if (online > now) {
|
||||
return lang(lng_status_online);
|
||||
}
|
||||
return base::none;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
inline auto AdminRightsValue(not_null<ChannelData*> channel) {
|
||||
return channel->adminRightsValue();
|
||||
@@ -123,4 +183,129 @@ rpl::producer<bool> CanWriteValue(not_null<PeerData*> peer) {
|
||||
Unexpected("Bad peer value in CanWriteValue()");
|
||||
}
|
||||
|
||||
TimeId SortByOnlineValue(not_null<UserData*> user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return -1;
|
||||
}
|
||||
const auto online = user->onlineTill;
|
||||
const auto fromDate = [](const QDate &date) {
|
||||
const auto shift = (unixtime() - myunixtime());
|
||||
return static_cast<TimeId>(QDateTime(date).toTime_t()) + shift;
|
||||
};
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
case -1: return online;
|
||||
|
||||
case -2: {
|
||||
const auto recently = date(now).date().addDays(-3);
|
||||
return fromDate(recently);
|
||||
} break;
|
||||
|
||||
case -3: {
|
||||
const auto weekago = date(now).date().addDays(-7);
|
||||
return fromDate(weekago);
|
||||
} break;
|
||||
|
||||
case -4: {
|
||||
const auto monthago = date(now).date().addDays(-30);
|
||||
return fromDate(monthago);
|
||||
} break;
|
||||
}
|
||||
return -online;
|
||||
}
|
||||
return online;
|
||||
}
|
||||
|
||||
TimeMs OnlineChangeTimeout(TimeId online, TimeId now) {
|
||||
const auto result = OnlinePhraseChangeInSeconds(online, now);
|
||||
Assert(result >= 0);
|
||||
return snap(
|
||||
result * TimeMs(1000),
|
||||
kMinOnlineChangeTimeout,
|
||||
kMaxOnlineChangeTimeout);
|
||||
}
|
||||
|
||||
TimeMs OnlineChangeTimeout(not_null<UserData*> user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return kMaxOnlineChangeTimeout;
|
||||
}
|
||||
return OnlineChangeTimeout(user->onlineTill, now);
|
||||
}
|
||||
|
||||
QString OnlineText(TimeId online, TimeId now) {
|
||||
if (const auto common = OnlineTextCommon(online, now)) {
|
||||
return *common;
|
||||
}
|
||||
const auto minutes = (now - online) / 60;
|
||||
if (!minutes) {
|
||||
return lang(lng_status_lastseen_now);
|
||||
} else if (minutes < 60) {
|
||||
return lng_status_lastseen_minutes(lt_count, minutes);
|
||||
}
|
||||
const auto hours = (now - online) / 3600;
|
||||
if (hours < 12) {
|
||||
return lng_status_lastseen_hours(lt_count, hours);
|
||||
}
|
||||
const auto onlineFull = ::date(online);
|
||||
const auto nowFull = ::date(now);
|
||||
if (onlineFull.date() == nowFull.date()) {
|
||||
const auto onlineTime = onlineFull.time().toString(cTimeFormat());
|
||||
return lng_status_lastseen_today(lt_time, onlineTime);
|
||||
} else if (onlineFull.date().addDays(1) == nowFull.date()) {
|
||||
const auto onlineTime = onlineFull.time().toString(cTimeFormat());
|
||||
return lng_status_lastseen_yesterday(lt_time, onlineTime);
|
||||
}
|
||||
const auto date = onlineFull.date().toString(qsl("dd.MM.yy"));
|
||||
return lng_status_lastseen_date(lt_date, date);
|
||||
}
|
||||
|
||||
QString OnlineText(not_null<UserData*> user, TimeId now) {
|
||||
if (const auto special = OnlineTextSpecial(user)) {
|
||||
return *special;
|
||||
}
|
||||
return OnlineText(user->onlineTill, now);
|
||||
}
|
||||
|
||||
QString OnlineTextFull(not_null<UserData*> user, TimeId now) {
|
||||
if (const auto special = OnlineTextSpecial(user)) {
|
||||
return *special;
|
||||
} else if (const auto common = OnlineTextCommon(user->onlineTill, now)) {
|
||||
return *common;
|
||||
}
|
||||
const auto onlineFull = ::date(user->onlineTill);
|
||||
const auto nowFull = ::date(now);
|
||||
if (onlineFull.date() == nowFull.date()) {
|
||||
const auto onlineTime = onlineFull.time().toString(cTimeFormat());
|
||||
return lng_status_lastseen_today(lt_time, onlineTime);
|
||||
} else if (onlineFull.date().addDays(1) == nowFull.date()) {
|
||||
const auto onlineTime = onlineFull.time().toString(cTimeFormat());
|
||||
return lng_status_lastseen_yesterday(lt_time, onlineTime);
|
||||
}
|
||||
const auto date = onlineFull.date().toString(qsl("dd.MM.yy"));
|
||||
const auto time = onlineFull.time().toString(cTimeFormat());
|
||||
return lng_status_lastseen_date_time(lt_date, date, lt_time, time);
|
||||
}
|
||||
|
||||
bool OnlineTextActive(TimeId online, TimeId now) {
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
case -1:
|
||||
case -2:
|
||||
case -3:
|
||||
case -4: return false;
|
||||
}
|
||||
return (-online > now);
|
||||
}
|
||||
return (online > now);
|
||||
}
|
||||
|
||||
bool OnlineTextActive(not_null<UserData*> user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return false;
|
||||
}
|
||||
return OnlineTextActive(user->onlineTill, now);
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -31,23 +31,25 @@ template <typename ChangeType, typename Error, typename Generator>
|
||||
inline auto FlagsValueWithMask(
|
||||
rpl::producer<ChangeType, Error, Generator> &&value,
|
||||
typename ChangeType::Type mask) {
|
||||
return std::move(value)
|
||||
| rpl::filter([mask](const ChangeType &change) {
|
||||
return change.diff & mask;
|
||||
})
|
||||
| rpl::map([mask](const ChangeType &change) {
|
||||
return change.value & mask;
|
||||
});
|
||||
return std::move(
|
||||
value
|
||||
) | rpl::filter([mask](const ChangeType &change) {
|
||||
return change.diff & mask;
|
||||
}) | rpl::map([mask](const ChangeType &change) {
|
||||
return change.value & mask;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename ChangeType, typename Error, typename Generator>
|
||||
inline auto SingleFlagValue(
|
||||
rpl::producer<ChangeType, Error, Generator> &&value,
|
||||
typename ChangeType::Enum flag) {
|
||||
return FlagsValueWithMask(std::move(value), flag)
|
||||
| rpl::map([flag](typename ChangeType::Type value) {
|
||||
return !!value;
|
||||
});
|
||||
return FlagsValueWithMask(
|
||||
std::move(value),
|
||||
flag
|
||||
) | rpl::map([flag](typename ChangeType::Type value) {
|
||||
return !!value;
|
||||
});
|
||||
}
|
||||
|
||||
template <
|
||||
@@ -121,4 +123,13 @@ rpl::producer<bool> CanWriteValue(ChatData *chat);
|
||||
rpl::producer<bool> CanWriteValue(ChannelData *channel);
|
||||
rpl::producer<bool> CanWriteValue(not_null<PeerData*> peer);
|
||||
|
||||
TimeId SortByOnlineValue(not_null<UserData*> user, TimeId now);
|
||||
TimeMs OnlineChangeTimeout(TimeId online, TimeId now);
|
||||
TimeMs OnlineChangeTimeout(not_null<UserData*> user, TimeId now);
|
||||
QString OnlineText(TimeId online, TimeId now);
|
||||
QString OnlineText(not_null<UserData*> user, TimeId now);
|
||||
QString OnlineTextFull(not_null<UserData*> user, TimeId now);
|
||||
bool OnlineTextActive(TimeId online, TimeId now);
|
||||
bool OnlineTextActive(not_null<UserData*> user, TimeId now);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -63,7 +63,9 @@ bool PhotoData::loading() const {
|
||||
}
|
||||
|
||||
bool PhotoData::displayLoading() const {
|
||||
return full->loading() ? full->displayLoading() : uploading();
|
||||
return full->loading()
|
||||
? full->displayLoading()
|
||||
: (uploading() && !waitingForAlbum());
|
||||
}
|
||||
|
||||
void PhotoData::cancel() {
|
||||
@@ -91,12 +93,22 @@ float64 PhotoData::progress() const {
|
||||
return full->progress();
|
||||
}
|
||||
|
||||
void PhotoData::setWaitingForAlbum() {
|
||||
if (uploading()) {
|
||||
uploadingData->waitingForAlbum = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool PhotoData::waitingForAlbum() const {
|
||||
return uploading() && uploadingData->waitingForAlbum;
|
||||
}
|
||||
|
||||
int32 PhotoData::loadOffset() const {
|
||||
return full->loadOffset();
|
||||
}
|
||||
|
||||
bool PhotoData::uploading() const {
|
||||
return !!uploadingData;
|
||||
return (uploadingData != nullptr);
|
||||
}
|
||||
|
||||
void PhotoData::forget() {
|
||||
@@ -121,7 +133,7 @@ ImagePtr PhotoData::makeReplyPreview() {
|
||||
}
|
||||
|
||||
void PhotoOpenClickHandler::onClickImpl() const {
|
||||
Messenger::Instance().showPhoto(this, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem());
|
||||
Messenger::Instance().showPhoto(this);
|
||||
}
|
||||
|
||||
void PhotoSaveClickHandler::onClickImpl() const {
|
||||
@@ -136,13 +148,9 @@ void PhotoCancelClickHandler::onClickImpl() const {
|
||||
if (!data->date) return;
|
||||
|
||||
if (data->uploading()) {
|
||||
if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) {
|
||||
if (auto media = item->getMedia()) {
|
||||
if (media->type() == MediaTypePhoto && static_cast<HistoryPhoto*>(media)->photo() == data) {
|
||||
App::contextItem(item);
|
||||
App::main()->cancelUploadLayer();
|
||||
}
|
||||
}
|
||||
if (const auto item = App::histItemById(context())) {
|
||||
App::contextItem(item);
|
||||
App::main()->cancelUploadLayer();
|
||||
}
|
||||
} else {
|
||||
data->cancel();
|
||||
|
||||
@@ -44,6 +44,9 @@ public:
|
||||
int32 loadOffset() const;
|
||||
bool uploading() const;
|
||||
|
||||
void setWaitingForAlbum();
|
||||
bool waitingForAlbum() const;
|
||||
|
||||
void forget();
|
||||
ImagePtr makeReplyPreview();
|
||||
|
||||
@@ -57,25 +60,22 @@ public:
|
||||
PeerData *peer = nullptr; // for chat and channel photos connection
|
||||
// geo, caption
|
||||
|
||||
struct UploadingData {
|
||||
UploadingData(int size) : size(size) {
|
||||
}
|
||||
int offset = 0;
|
||||
int size = 0;
|
||||
};
|
||||
std::unique_ptr<UploadingData> uploadingData;
|
||||
std::unique_ptr<Data::UploadState> uploadingData;
|
||||
|
||||
private:
|
||||
void notifyLayoutChanged() const;
|
||||
|
||||
};
|
||||
|
||||
class PhotoClickHandler : public LeftButtonClickHandler {
|
||||
class PhotoClickHandler : public FileClickHandler {
|
||||
public:
|
||||
PhotoClickHandler(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context = FullMsgId(),
|
||||
PeerData *peer = nullptr)
|
||||
: _photo(photo), _peer(peer) {
|
||||
: FileClickHandler(context)
|
||||
, _photo(photo)
|
||||
, _peer(peer) {
|
||||
}
|
||||
not_null<PhotoData*> photo() const {
|
||||
return _photo;
|
||||
@@ -86,7 +86,7 @@ public:
|
||||
|
||||
private:
|
||||
not_null<PhotoData*> _photo;
|
||||
PeerData *_peer;
|
||||
PeerData *_peer = nullptr;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -36,13 +36,15 @@ MTPmessages_Search PrepareSearchRequest(
|
||||
const QString &query,
|
||||
MsgId messageId,
|
||||
SparseIdsLoadDirection direction) {
|
||||
auto filter = [&] {
|
||||
const auto filter = [&] {
|
||||
using Type = Storage::SharedMediaType;
|
||||
switch (type) {
|
||||
case Type::Photo:
|
||||
return MTP_inputMessagesFilterPhotos();
|
||||
case Type::Video:
|
||||
return MTP_inputMessagesFilterVideo();
|
||||
case Type::PhotoVideo:
|
||||
return MTP_inputMessagesFilterPhotoVideo();
|
||||
case Type::MusicFile:
|
||||
return MTP_inputMessagesFilterMusic();
|
||||
case Type::File:
|
||||
@@ -63,10 +65,10 @@ MTPmessages_Search PrepareSearchRequest(
|
||||
return MTP_inputMessagesFilterEmpty();
|
||||
}();
|
||||
|
||||
auto minId = 0;
|
||||
auto maxId = 0;
|
||||
auto limit = messageId ? kSharedMediaLimit : 0;
|
||||
auto offsetId = [&] {
|
||||
const auto minId = 0;
|
||||
const auto maxId = 0;
|
||||
const auto limit = messageId ? kSharedMediaLimit : 0;
|
||||
const auto offsetId = [&] {
|
||||
switch (direction) {
|
||||
case SparseIdsLoadDirection::Before:
|
||||
case SparseIdsLoadDirection::Around: return messageId;
|
||||
@@ -74,7 +76,7 @@ MTPmessages_Search PrepareSearchRequest(
|
||||
}
|
||||
Unexpected("Direction in PrepareSearchRequest");
|
||||
}();
|
||||
auto addOffset = [&] {
|
||||
const auto addOffset = [&] {
|
||||
switch (direction) {
|
||||
case SparseIdsLoadDirection::Before: return 0;
|
||||
case SparseIdsLoadDirection::Around: return -limit / 2;
|
||||
@@ -130,7 +132,8 @@ SearchResult ParseSearchResult(
|
||||
if (auto channel = peer->asChannel()) {
|
||||
channel->ptsReceived(d.vpts.v);
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when no channel was passed! (ParseSearchResult)"));
|
||||
LOG(("API Error: received messages.channelMessages when "
|
||||
"no channel was passed! (ParseSearchResult)"));
|
||||
}
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
@@ -139,7 +142,8 @@ SearchResult ParseSearchResult(
|
||||
} break;
|
||||
|
||||
case mtpc_messages_messagesNotModified: {
|
||||
LOG(("API Error: received messages.messagesNotModified! (ParseSearchResult)"));
|
||||
LOG(("API Error: received messages.messagesNotModified! "
|
||||
"(ParseSearchResult)"));
|
||||
return (const QVector<MTPMessage>*)nullptr;
|
||||
} break;
|
||||
}
|
||||
@@ -260,50 +264,46 @@ rpl::producer<SparseIdsSlice> SearchController::simpleIdsSlice(
|
||||
aroundId,
|
||||
limitBefore,
|
||||
limitAfter);
|
||||
builder->insufficientAround()
|
||||
| rpl::start_with_next([=](
|
||||
const SparseIdsSliceBuilder::AroundData &data) {
|
||||
requestMore(data, query, listData);
|
||||
}, lifetime);
|
||||
builder->insufficientAround(
|
||||
) | rpl::start_with_next([=](
|
||||
const SparseIdsSliceBuilder::AroundData &data) {
|
||||
requestMore(data, query, listData);
|
||||
}, lifetime);
|
||||
|
||||
auto pushNextSnapshot = [=] {
|
||||
consumer.put_next(builder->snapshot());
|
||||
};
|
||||
|
||||
listData->list.sliceUpdated()
|
||||
| rpl::filter([=](const SliceUpdate &update) {
|
||||
return builder->applyUpdate(update);
|
||||
})
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
listData->list.sliceUpdated(
|
||||
) | rpl::filter([=](const SliceUpdate &update) {
|
||||
return builder->applyUpdate(update);
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
Auth().data().itemRemoved()
|
||||
| rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return (item->history()->peer->id == peerId);
|
||||
})
|
||||
| rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return builder->removeOne(item->id);
|
||||
})
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
Auth().data().itemRemoved(
|
||||
) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return (item->history()->peer->id == peerId);
|
||||
}) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return builder->removeOne(item->id);
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
Auth().data().historyCleared()
|
||||
| rpl::filter([=](not_null<const History*> history) {
|
||||
return (history->peer->id == peerId);
|
||||
})
|
||||
| rpl::filter([=] { return builder->removeAll(); })
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
Auth().data().historyCleared(
|
||||
) | rpl::filter([=](not_null<const History*> history) {
|
||||
return (history->peer->id == peerId);
|
||||
}) | rpl::filter([=] {
|
||||
return builder->removeAll();
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
using Result = Storage::SparseIdsListResult;
|
||||
listData->list.query(Storage::SparseIdsListQuery(
|
||||
aroundId,
|
||||
limitBefore,
|
||||
limitAfter))
|
||||
| rpl::filter([=](const Result &result) {
|
||||
return builder->applyInitial(result);
|
||||
})
|
||||
| rpl::start_with_next_done(
|
||||
pushNextSnapshot,
|
||||
[=] { builder->checkInsufficient(); },
|
||||
lifetime);
|
||||
limitAfter
|
||||
)) | rpl::filter([=](const Result &result) {
|
||||
return builder->applyInitial(result);
|
||||
}) | rpl::start_with_next_done(
|
||||
pushNextSnapshot,
|
||||
[=] { builder->checkInsufficient(); },
|
||||
lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
|
||||
@@ -93,56 +93,50 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
||||
data.aroundId,
|
||||
data.direction);
|
||||
};
|
||||
builder->insufficientAround()
|
||||
| rpl::start_with_next(requestMediaAround, lifetime);
|
||||
builder->insufficientAround(
|
||||
) | rpl::start_with_next(requestMediaAround, lifetime);
|
||||
|
||||
auto pushNextSnapshot = [=] {
|
||||
consumer.put_next(builder->snapshot());
|
||||
};
|
||||
|
||||
using SliceUpdate = Storage::SharedMediaSliceUpdate;
|
||||
Auth().storage().sharedMediaSliceUpdated()
|
||||
| rpl::filter([=](const SliceUpdate &update) {
|
||||
return (update.peerId == key.peerId)
|
||||
&& (update.type == key.type);
|
||||
})
|
||||
| rpl::filter([=](const SliceUpdate &update) {
|
||||
return builder->applyUpdate(update.data);
|
||||
})
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
Auth().storage().sharedMediaSliceUpdated(
|
||||
) | rpl::filter([=](const SliceUpdate &update) {
|
||||
return (update.peerId == key.peerId)
|
||||
&& (update.type == key.type);
|
||||
}) | rpl::filter([=](const SliceUpdate &update) {
|
||||
return builder->applyUpdate(update.data);
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
using OneRemoved = Storage::SharedMediaRemoveOne;
|
||||
Auth().storage().sharedMediaOneRemoved()
|
||||
| rpl::filter([=](const OneRemoved &update) {
|
||||
return (update.peerId == key.peerId)
|
||||
&& update.types.test(key.type);
|
||||
})
|
||||
| rpl::filter([=](const OneRemoved &update) {
|
||||
return builder->removeOne(update.messageId);
|
||||
})
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
Auth().storage().sharedMediaOneRemoved(
|
||||
) | rpl::filter([=](const OneRemoved &update) {
|
||||
return (update.peerId == key.peerId)
|
||||
&& update.types.test(key.type);
|
||||
}) | rpl::filter([=](const OneRemoved &update) {
|
||||
return builder->removeOne(update.messageId);
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
using AllRemoved = Storage::SharedMediaRemoveAll;
|
||||
Auth().storage().sharedMediaAllRemoved()
|
||||
| rpl::filter([=](const AllRemoved &update) {
|
||||
return (update.peerId == key.peerId);
|
||||
})
|
||||
| rpl::filter([=] { return builder->removeAll(); })
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
Auth().storage().sharedMediaAllRemoved(
|
||||
) | rpl::filter([=](const AllRemoved &update) {
|
||||
return (update.peerId == key.peerId);
|
||||
}) | rpl::filter([=] {
|
||||
return builder->removeAll();
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
using Result = Storage::SharedMediaResult;
|
||||
Auth().storage().query(
|
||||
Storage::SharedMediaQuery(
|
||||
key,
|
||||
limitBefore,
|
||||
limitAfter))
|
||||
| rpl::filter([=](const Result &result) {
|
||||
return builder->applyInitial(result);
|
||||
})
|
||||
| rpl::start_with_next_done(
|
||||
pushNextSnapshot,
|
||||
[=] { builder->checkInsufficient(); },
|
||||
lifetime);
|
||||
Auth().storage().query(Storage::SharedMediaQuery(
|
||||
key,
|
||||
limitBefore,
|
||||
limitAfter
|
||||
)) | rpl::filter([=](const Result &result) {
|
||||
return builder->applyInitial(result);
|
||||
}) | rpl::start_with_next_done(
|
||||
pushNextSnapshot,
|
||||
[=] { builder->checkInsufficient(); },
|
||||
lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
@@ -290,9 +284,7 @@ base::optional<bool> SharedMediaWithLastSlice::IsLastIsolated(
|
||||
| [](FullMsgId msgId) { return App::histItemById(msgId); }
|
||||
| [](HistoryItem *item) { return item ? item->getMedia() : nullptr; }
|
||||
| [](HistoryMedia *media) {
|
||||
return (media && media->type() == MediaTypePhoto)
|
||||
? static_cast<HistoryPhoto*>(media)->photo()
|
||||
: nullptr;
|
||||
return media ? media->getPhoto() : nullptr;
|
||||
}
|
||||
| [](PhotoData *photo) { return photo ? photo->id : 0; }
|
||||
| [&](PhotoId photoId) { return *lastPeerPhotoId != photoId; };
|
||||
@@ -355,9 +347,12 @@ rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastReversedViewer(
|
||||
SharedMediaWithLastSlice::Key key,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
return SharedMediaWithLastViewer(key, limitBefore, limitAfter)
|
||||
| rpl::map([](SharedMediaWithLastSlice &&slice) {
|
||||
slice.reverse();
|
||||
return std::move(slice);
|
||||
});
|
||||
return SharedMediaWithLastViewer(
|
||||
key,
|
||||
limitBefore,
|
||||
limitAfter
|
||||
) | rpl::map([](SharedMediaWithLastSlice &&slice) {
|
||||
slice.reverse();
|
||||
return std::move(slice);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -415,13 +415,14 @@ rpl::producer<SparseIdsMergedSlice> SparseIdsMergedSlice::CreateViewer(
|
||||
limitAfter
|
||||
);
|
||||
if (!key.migratedPeerId) {
|
||||
return std::move(partViewer)
|
||||
| rpl::start_with_next([=](SparseIdsSlice &&part) {
|
||||
consumer.put_next(SparseIdsMergedSlice(
|
||||
key,
|
||||
std::move(part),
|
||||
base::none));
|
||||
});
|
||||
return std::move(
|
||||
partViewer
|
||||
) | rpl::start_with_next([=](SparseIdsSlice &&part) {
|
||||
consumer.put_next(SparseIdsMergedSlice(
|
||||
key,
|
||||
std::move(part),
|
||||
base::none));
|
||||
});
|
||||
}
|
||||
auto migratedViewer = simpleViewer(
|
||||
key.migratedPeerId,
|
||||
|
||||
@@ -53,3 +53,13 @@ void MessageCursor::applyTo(QTextEdit *edit) {
|
||||
scrollbar->setValue(scroll);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryItem *FileClickHandler::getActionItem() const {
|
||||
return context()
|
||||
? App::histItemById(context())
|
||||
: App::hoveredLinkItem()
|
||||
? App::hoveredLinkItem()
|
||||
: App::contextItem()
|
||||
? App::contextItem()
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct UploadState {
|
||||
UploadState(int size) : size(size) {
|
||||
}
|
||||
int offset = 0;
|
||||
int size = 0;
|
||||
bool waitingForAlbum = false;
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
||||
class PeerData;
|
||||
class UserData;
|
||||
class ChatData;
|
||||
@@ -241,7 +253,6 @@ enum LocationType {
|
||||
enum FileStatus {
|
||||
FileDownloadFailed = -2,
|
||||
FileUploadFailed = -1,
|
||||
FileUploading = 0,
|
||||
FileReady = 1,
|
||||
};
|
||||
|
||||
@@ -390,3 +401,24 @@ struct SendAction {
|
||||
int progress = 0;
|
||||
|
||||
};
|
||||
|
||||
class FileClickHandler : public LeftButtonClickHandler {
|
||||
public:
|
||||
FileClickHandler(FullMsgId context) : _context(context) {
|
||||
}
|
||||
|
||||
void setMessageId(FullMsgId context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
FullMsgId context() const {
|
||||
return _context;
|
||||
}
|
||||
|
||||
protected:
|
||||
HistoryItem *getActionItem() const;
|
||||
|
||||
private:
|
||||
FullMsgId _context;
|
||||
|
||||
};
|
||||
|
||||
@@ -233,11 +233,11 @@ rpl::producer<UserPhotosSlice> UserPhotosViewer(
|
||||
Auth().storage().query(Storage::UserPhotosQuery(
|
||||
key,
|
||||
limitBefore,
|
||||
limitAfter))
|
||||
| rpl::start_with_next_done(
|
||||
applyUpdate,
|
||||
[=] { builder->checkInsufficientPhotos(); },
|
||||
lifetime);
|
||||
limitAfter
|
||||
)) | rpl::start_with_next_done(
|
||||
applyUpdate,
|
||||
[=] { builder->checkInsufficientPhotos(); },
|
||||
lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
@@ -248,9 +248,12 @@ rpl::producer<UserPhotosSlice> UserPhotosReversedViewer(
|
||||
UserPhotosSlice::Key key,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
return UserPhotosViewer(key, limitBefore, limitAfter)
|
||||
| rpl::map([](UserPhotosSlice &&slice) {
|
||||
slice.reverse();
|
||||
return std::move(slice);
|
||||
});
|
||||
return UserPhotosViewer(
|
||||
key,
|
||||
limitBefore,
|
||||
limitAfter
|
||||
) | rpl::map([](UserPhotosSlice &&slice) {
|
||||
slice.reverse();
|
||||
return std::move(slice);
|
||||
});
|
||||
}
|
||||
122
Telegram/SourceFiles/data/data_web_page.cpp
Normal file
122
Telegram/SourceFiles/data/data_web_page.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "data/data_web_page.h"
|
||||
|
||||
#include "auth_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "mainwidget.h"
|
||||
#include "ui/text/text_entity.h"
|
||||
|
||||
namespace {
|
||||
|
||||
QString SiteNameFromUrl(const QString &url) {
|
||||
QUrl u(url);
|
||||
QString pretty = u.isValid() ? u.toDisplayString() : url;
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^[a-zA-Z0-9]+://")).match(pretty);
|
||||
if (m.hasMatch()) pretty = pretty.mid(m.capturedLength());
|
||||
int32 slash = pretty.indexOf('/');
|
||||
if (slash > 0) pretty = pretty.mid(0, slash);
|
||||
QStringList components = pretty.split('.', QString::SkipEmptyParts);
|
||||
if (components.size() >= 2) {
|
||||
components = components.mid(components.size() - 2);
|
||||
return components.at(0).at(0).toUpper() + components.at(0).mid(1) + '.' + components.at(1);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool WebPageData::applyChanges(
|
||||
const QString &newType,
|
||||
const QString &newUrl,
|
||||
const QString &newDisplayUrl,
|
||||
const QString &newSiteName,
|
||||
const QString &newTitle,
|
||||
const TextWithEntities &newDescription,
|
||||
PhotoData *newPhoto,
|
||||
DocumentData *newDocument,
|
||||
int newDuration,
|
||||
const QString &newAuthor,
|
||||
int newPendingTill) {
|
||||
if (newPendingTill != 0
|
||||
&& (!url.isEmpty() || newUrl.isEmpty())
|
||||
&& (!pendingTill
|
||||
|| pendingTill == newPendingTill
|
||||
|| newPendingTill < -1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto resultType = toWebPageType(newType);
|
||||
const auto resultUrl = TextUtilities::Clean(newUrl);
|
||||
const auto resultDisplayUrl = TextUtilities::Clean(
|
||||
newDisplayUrl);
|
||||
const auto possibleSiteName = TextUtilities::Clean(
|
||||
newSiteName);
|
||||
const auto resultTitle = TextUtilities::SingleLine(
|
||||
newTitle);
|
||||
const auto resultAuthor = TextUtilities::Clean(newAuthor);
|
||||
|
||||
const auto viewTitleText = resultTitle.isEmpty()
|
||||
? TextUtilities::SingleLine(resultAuthor)
|
||||
: resultTitle;
|
||||
const auto resultSiteName = [&] {
|
||||
if (!possibleSiteName.isEmpty()) {
|
||||
return possibleSiteName;
|
||||
} else if (!newDescription.text.isEmpty()
|
||||
&& viewTitleText.isEmpty()
|
||||
&& !resultUrl.isEmpty()) {
|
||||
return SiteNameFromUrl(resultUrl);
|
||||
}
|
||||
return QString();
|
||||
}();
|
||||
|
||||
if (type == resultType
|
||||
&& url == resultUrl
|
||||
&& displayUrl == resultDisplayUrl
|
||||
&& siteName == resultSiteName
|
||||
&& title == resultTitle
|
||||
&& description.text == newDescription.text
|
||||
&& photo == newPhoto
|
||||
&& document == newDocument
|
||||
&& duration == newDuration
|
||||
&& author == resultAuthor
|
||||
&& pendingTill == newPendingTill) {
|
||||
return false;
|
||||
}
|
||||
if (pendingTill > 0 && newPendingTill <= 0) {
|
||||
Auth().api().clearWebPageRequest(this);
|
||||
}
|
||||
type = resultType;
|
||||
url = resultUrl;
|
||||
displayUrl = resultDisplayUrl;
|
||||
siteName = resultSiteName;
|
||||
title = resultTitle;
|
||||
description = newDescription;
|
||||
photo = newPhoto;
|
||||
document = newDocument;
|
||||
duration = newDuration;
|
||||
author = resultAuthor;
|
||||
pendingTill = newPendingTill;
|
||||
++version;
|
||||
if (App::main()) App::main()->webPageUpdated(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -50,9 +50,9 @@ struct WebPageData {
|
||||
const TextWithEntities &description,
|
||||
DocumentData *document,
|
||||
PhotoData *photo,
|
||||
int32 duration,
|
||||
int duration,
|
||||
const QString &author,
|
||||
int32 pendingTill)
|
||||
int pendingTill)
|
||||
: id(id)
|
||||
, type(type)
|
||||
, url(url)
|
||||
@@ -72,6 +72,19 @@ struct WebPageData {
|
||||
if (photo) photo->forget();
|
||||
}
|
||||
|
||||
bool applyChanges(
|
||||
const QString &newType,
|
||||
const QString &newUrl,
|
||||
const QString &newDisplayUrl,
|
||||
const QString &newSiteName,
|
||||
const QString &newTitle,
|
||||
const TextWithEntities &newDescription,
|
||||
PhotoData *newPhoto,
|
||||
DocumentData *newDocument,
|
||||
int newDuration,
|
||||
const QString &newAuthor,
|
||||
int newPendingTill);
|
||||
|
||||
WebPageId id = 0;
|
||||
WebPageType type = WebPageArticle;
|
||||
QString url;
|
||||
@@ -79,10 +92,11 @@ struct WebPageData {
|
||||
QString siteName;
|
||||
QString title;
|
||||
TextWithEntities description;
|
||||
int32 duration = 0;
|
||||
int duration = 0;
|
||||
QString author;
|
||||
PhotoData *photo = nullptr;
|
||||
DocumentData *document = nullptr;
|
||||
int32 pendingTill = 0;
|
||||
int pendingTill = 0;
|
||||
int version = 0;
|
||||
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "styles/style_window.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
@@ -36,6 +37,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "apiwrap.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "observer_peer.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
#include "auth_session.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/window_controller.h"
|
||||
@@ -95,16 +97,16 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
|
||||
_cancelSearchFromUser->hide();
|
||||
|
||||
subscribe(Auth().downloaderTaskFinished(), [this] { update(); });
|
||||
Auth().data().itemRemoved()
|
||||
| rpl::start_with_next(
|
||||
[this](auto item) { itemRemoved(item); },
|
||||
lifetime());
|
||||
Auth().data().itemRepaintRequest()
|
||||
| rpl::start_with_next([this](auto item) {
|
||||
if (item->history()->lastMsg == item) {
|
||||
item->history()->updateChatListEntry();
|
||||
}
|
||||
}, lifetime());
|
||||
Auth().data().itemRemoved(
|
||||
) | rpl::start_with_next(
|
||||
[this](auto item) { itemRemoved(item); },
|
||||
lifetime());
|
||||
Auth().data().itemRepaintRequest(
|
||||
) | rpl::start_with_next([this](auto item) {
|
||||
if (item->history()->lastMsg == item) {
|
||||
item->history()->updateChatListEntry();
|
||||
}
|
||||
}, lifetime());
|
||||
subscribe(App::histories().sendActionAnimationUpdated(), [this](const Histories::SendActionAnimationUpdate &update) {
|
||||
auto updateRect = Dialogs::Layout::RowPainter::sendActionAnimationRect(update.width, update.height, getFullWidth(), update.textUpdated);
|
||||
updateDialogRow(update.history->peer, MsgId(0), updateRect, UpdateRowSection::Default | UpdateRowSection::Filtered);
|
||||
@@ -1796,9 +1798,9 @@ void DialogsInner::searchInPeer(PeerData *peer, UserData *from) {
|
||||
_cancelSearchInPeer->show();
|
||||
if (_searchInPeer->isSelf()) {
|
||||
_searchInSavedText.setText(
|
||||
st::dialogsSearchFromStyle,
|
||||
st::msgNameStyle,
|
||||
lang(lng_saved_messages),
|
||||
_textDlgOptions);
|
||||
Ui::DialogTextOptions());
|
||||
}
|
||||
} else {
|
||||
_cancelSearchInPeer->hide();
|
||||
@@ -1810,7 +1812,7 @@ void DialogsInner::searchInPeer(PeerData *peer, UserData *from) {
|
||||
_searchFromUserText.setText(
|
||||
st::dialogsSearchFromStyle,
|
||||
fromUserText,
|
||||
_textDlgOptions);
|
||||
Ui::DialogTextOptions());
|
||||
_cancelSearchFromUser->show();
|
||||
} else {
|
||||
_cancelSearchFromUser->hide();
|
||||
@@ -2123,7 +2125,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) {
|
||||
recent = cRecentSearchHashtags();
|
||||
}
|
||||
found = true;
|
||||
incrementRecentHashtag(recent, text.mid(i + 1, next - i - 1));
|
||||
Stickers::IncrementRecentHashtag(recent, text.mid(i + 1, next - i - 1));
|
||||
}
|
||||
if (found) {
|
||||
cSetRecentSearchHashtags(recent);
|
||||
|
||||
@@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
namespace Dialogs {
|
||||
@@ -156,7 +157,7 @@ void paintRow(
|
||||
if (history->cloudDraftTextCache.isEmpty()) {
|
||||
auto draftWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, lang(lng_from_draft)));
|
||||
auto draftText = lng_dialogs_text_with_from(lt_from_part, draftWrapped, lt_message, TextUtilities::Clean(draft->textWithTags.text));
|
||||
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, _textDlgOptions);
|
||||
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, Ui::DialogTextOptions());
|
||||
}
|
||||
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
||||
p.setTextPalette(active ? st::dialogsTextPaletteDraftActive : (selected ? st::dialogsTextPaletteDraftOver : st::dialogsTextPaletteDraft));
|
||||
@@ -499,7 +500,7 @@ void RowPainter::paint(
|
||||
auto item = row->item();
|
||||
auto history = item->history();
|
||||
auto cloudDraft = nullptr;
|
||||
auto from = [&] {
|
||||
const auto from = [&] {
|
||||
if (auto searchPeer = row->searchInPeer()) {
|
||||
if (searchPeer->isSelf()) {
|
||||
return item->senderOriginal();
|
||||
@@ -509,7 +510,7 @@ void RowPainter::paint(
|
||||
}
|
||||
return (history->peer->migrateTo() ? history->peer->migrateTo() : history->peer);
|
||||
}();
|
||||
auto drawInDialogWay = [&] {
|
||||
const auto drawInDialogWay = [&] {
|
||||
if (auto searchPeer = row->searchInPeer()) {
|
||||
if (!searchPeer->isChannel() || searchPeer->isMegagroup()) {
|
||||
return HistoryItem::DrawInDialog::WithoutSender;
|
||||
@@ -517,7 +518,7 @@ void RowPainter::paint(
|
||||
}
|
||||
return HistoryItem::DrawInDialog::Normal;
|
||||
}();
|
||||
auto paintItemCallback = [&](int nameleft, int namewidth) {
|
||||
const auto paintItemCallback = [&](int nameleft, int namewidth) {
|
||||
auto lastWidth = namewidth;
|
||||
auto texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip;
|
||||
item->drawInDialog(
|
||||
@@ -529,11 +530,14 @@ void RowPainter::paint(
|
||||
row->_cacheFor,
|
||||
row->_cache);
|
||||
};
|
||||
auto paintCounterCallback = [] {};
|
||||
const auto paintCounterCallback = [] {};
|
||||
const auto showSavedMessages = history->peer->isSelf()
|
||||
&& !row->searchInPeer();
|
||||
const auto flags = (active ? Flag::Active : Flag(0))
|
||||
| (selected ? Flag::Selected : Flag(0))
|
||||
| (onlyBackground ? Flag::OnlyBackground : Flag(0))
|
||||
| Flag::SearchResult;
|
||||
| Flag::SearchResult
|
||||
| (showSavedMessages ? Flag::SavedMessages : Flag(0));
|
||||
paintRow(
|
||||
p,
|
||||
row,
|
||||
|
||||
@@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "observer_peer.h"
|
||||
#include "auth_session.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -94,19 +95,22 @@ void ChatSearchFromController::rebuildRows() {
|
||||
auto wasEmpty = !delegate()->peerListFullRowsCount();
|
||||
|
||||
auto now = unixtime();
|
||||
QMultiMap<int32, UserData*> ordered;
|
||||
const auto byOnline = [&](not_null<UserData*> user) {
|
||||
return Data::SortByOnlineValue(user, now);
|
||||
};
|
||||
auto ordered = QMultiMap<TimeId, not_null<UserData*>>();
|
||||
if (_chat->noParticipantInfo()) {
|
||||
Auth().api().requestFullPeer(_chat);
|
||||
} else if (!_chat->participants.empty()) {
|
||||
for (const auto [user, version] : _chat->participants) {
|
||||
ordered.insertMulti(App::onlineForSort(user, now), user);
|
||||
ordered.insertMulti(byOnline(user), user);
|
||||
}
|
||||
}
|
||||
for_const (auto user, _chat->lastAuthors) {
|
||||
if (user->isInaccessible()) continue;
|
||||
appendRow(user);
|
||||
if (!ordered.isEmpty()) {
|
||||
ordered.remove(App::onlineForSort(user, now), user);
|
||||
ordered.remove(byOnline(user), user);
|
||||
}
|
||||
}
|
||||
if (!ordered.isEmpty()) {
|
||||
|
||||
@@ -38,6 +38,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_slide_animation.h"
|
||||
#include "profile/profile_channel_controllers.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -228,7 +229,7 @@ void DialogsWidget::startWidthAnimation() {
|
||||
st::columnMinimalWidthLeft,
|
||||
scrollGeometry.height());
|
||||
_scroll->setGeometry(grabGeometry);
|
||||
myEnsureResized(_scroll);
|
||||
Ui::SendPendingMoveResizeEvents(_scroll);
|
||||
auto image = QImage(
|
||||
grabGeometry.size() * cIntRetinaFactor(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
@@ -745,18 +746,22 @@ bool DialogsWidget::peopleFailed(const RPCError &error, mtpRequestId req) {
|
||||
}
|
||||
|
||||
void DialogsWidget::dragEnterEvent(QDragEnterEvent *e) {
|
||||
using namespace Storage;
|
||||
|
||||
if (App::main()->selectingPeer()) return;
|
||||
|
||||
const auto data = e->mimeData();
|
||||
_dragInScroll = false;
|
||||
_dragForward = e->mimeData()->hasFormat(qsl("application/x-td-forward-selected"));
|
||||
if (!_dragForward) _dragForward = e->mimeData()->hasFormat(qsl("application/x-td-forward-pressed-link"));
|
||||
if (!_dragForward) _dragForward = e->mimeData()->hasFormat(qsl("application/x-td-forward-pressed"));
|
||||
if (_dragForward && Adaptive::OneColumn()) _dragForward = false;
|
||||
_dragForward = Adaptive::OneColumn()
|
||||
? false
|
||||
: (data->hasFormat(qsl("application/x-td-forward-selected"))
|
||||
|| data->hasFormat(qsl("application/x-td-forward-pressed-link"))
|
||||
|| data->hasFormat(qsl("application/x-td-forward-pressed")));
|
||||
if (_dragForward) {
|
||||
e->setDropAction(Qt::CopyAction);
|
||||
e->accept();
|
||||
updateDragInScroll(_scroll->geometry().contains(e->pos()));
|
||||
} else if (App::main() && App::main()->getDragState(e->mimeData()) != DragState::None) {
|
||||
} else if (ComputeMimeDataState(data) != MimeDataState::None) {
|
||||
e->setDropAction(Qt::CopyAction);
|
||||
e->accept();
|
||||
}
|
||||
@@ -811,6 +816,7 @@ void DialogsWidget::dropEvent(QDropEvent *e) {
|
||||
if (auto peer = _inner->updateFromParentDrag(mapToGlobal(e->pos()))) {
|
||||
e->acceptProposedAction();
|
||||
App::main()->onFilesOrForwardDrop(peer->id, e->mimeData());
|
||||
controller()->window()->activateWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "core/click_handler_types.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "observer_peer.h"
|
||||
#include "mainwindow.h"
|
||||
#include "mainwidget.h"
|
||||
@@ -34,7 +35,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "window/layer_widget.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "base/observer.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "history/history_media.h"
|
||||
#include "styles/style_history.h"
|
||||
|
||||
@@ -69,19 +69,22 @@ bool insertBotCommand(const QString &cmd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void activateBotCommand(const HistoryItem *msg, int row, int col) {
|
||||
const HistoryMessageReplyMarkup::Button *button = nullptr;
|
||||
void activateBotCommand(
|
||||
not_null<const HistoryItem*> msg,
|
||||
int row,
|
||||
int column) {
|
||||
const HistoryMessageMarkupButton *button = nullptr;
|
||||
if (auto markup = msg->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (row < markup->rows.size()) {
|
||||
auto &buttonRow = markup->rows[row];
|
||||
if (col < buttonRow.size()) {
|
||||
button = &buttonRow.at(col);
|
||||
if (column < buttonRow.size()) {
|
||||
button = &buttonRow[column];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!button) return;
|
||||
|
||||
using ButtonType = HistoryMessageReplyMarkup::Button::Type;
|
||||
using ButtonType = HistoryMessageMarkupButton::Type;
|
||||
switch (button->type) {
|
||||
case ButtonType::Default: {
|
||||
// Copy string before passing it to the sending method
|
||||
@@ -93,7 +96,7 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) {
|
||||
case ButtonType::Callback:
|
||||
case ButtonType::Game: {
|
||||
if (auto m = main()) {
|
||||
m->app_sendBotCallback(button, msg, row, col);
|
||||
m->app_sendBotCallback(button, msg, row, column);
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -504,20 +507,7 @@ void WorkingDirReady() {
|
||||
}
|
||||
}
|
||||
|
||||
object_ptr<SingleQueuedInvokation> MainThreadTaskHandler = { nullptr };
|
||||
|
||||
void MainThreadTaskAdded() {
|
||||
if (!started()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MainThreadTaskHandler->call();
|
||||
}
|
||||
|
||||
void start() {
|
||||
MainThreadTaskHandler.create([] {
|
||||
base::TaskQueue::ProcessMainTasks();
|
||||
});
|
||||
SandboxData = std::make_unique<internal::Data>();
|
||||
}
|
||||
|
||||
@@ -527,7 +517,6 @@ bool started() {
|
||||
|
||||
void finish() {
|
||||
SandboxData.reset();
|
||||
MainThreadTaskHandler.destroy();
|
||||
}
|
||||
|
||||
uint64 UserTag() {
|
||||
|
||||
@@ -130,11 +130,21 @@ inline auto LambdaDelayedOnce(
|
||||
};
|
||||
}
|
||||
|
||||
void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo = 0);
|
||||
void sendBotCommand(
|
||||
PeerData *peer,
|
||||
UserData *bot,
|
||||
const QString &cmd,
|
||||
MsgId replyTo = 0);
|
||||
bool insertBotCommand(const QString &cmd);
|
||||
void activateBotCommand(const HistoryItem *msg, int row, int col);
|
||||
void activateBotCommand(
|
||||
not_null<const HistoryItem*> msg,
|
||||
int row,
|
||||
int column);
|
||||
void searchByHashtag(const QString &tag, PeerData *inPeer);
|
||||
void openPeerByName(const QString &username, MsgId msgId = ShowAtUnreadMsgId, const QString &startToken = QString());
|
||||
void openPeerByName(
|
||||
const QString &username,
|
||||
MsgId msgId = ShowAtUnreadMsgId,
|
||||
const QString &startToken = QString());
|
||||
void joinGroupByHash(const QString &hash);
|
||||
void removeDialog(History *history);
|
||||
void showSettings();
|
||||
|
||||
@@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "history/history_message.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_service.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "data/data_drafts.h"
|
||||
@@ -38,6 +39,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "data/data_channel_admins.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "core/crash_reports.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -60,7 +63,7 @@ auto GlobalPinnedIndex = 0;
|
||||
|
||||
HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from) {
|
||||
auto text = TextWithEntities { lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org")) };
|
||||
TextUtilities::ParseEntities(text, _historyTextNoMonoOptions.flags);
|
||||
TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags);
|
||||
text.entities.push_front(EntityInText(EntityInTextItalic, 0, text.text.size()));
|
||||
flags &= ~MTPDmessage::Flag::f_post_author;
|
||||
return HistoryMessage::create(history, msgId, flags, replyTo, viaBotId, date, from, QString(), text);
|
||||
@@ -68,11 +71,6 @@ HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage
|
||||
|
||||
} // namespace
|
||||
|
||||
void HistoryInit() {
|
||||
HistoryInitMessages();
|
||||
HistoryInitMedia();
|
||||
}
|
||||
|
||||
History::History(const PeerId &peerId)
|
||||
: peer(App::peer(peerId))
|
||||
, lastItemTextCache(st::dialogsTextWidthMin)
|
||||
@@ -362,7 +360,10 @@ bool History::updateSendActionNeedsAnimating(TimeMs ms, bool force) {
|
||||
}
|
||||
if (_sendActionString != newTypingString) {
|
||||
_sendActionString = newTypingString;
|
||||
_sendActionText.setText(st::dialogsTextStyle, _sendActionString, _textNameOptions);
|
||||
_sendActionText.setText(
|
||||
st::dialogsTextStyle,
|
||||
_sendActionString,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
}
|
||||
auto result = (!_typing.isEmpty() || !_sendActions.isEmpty());
|
||||
@@ -542,27 +543,6 @@ const QDateTime &ChannelHistory::maxReadMessageDate() {
|
||||
return _maxReadMessageDate;
|
||||
}
|
||||
|
||||
HistoryItem *ChannelHistory::addNewChannelMessage(const MTPMessage &msg, NewMessageType type) {
|
||||
if (type == NewMessageExisting) return addToHistory(msg);
|
||||
|
||||
return addNewToBlocks(msg, type);
|
||||
}
|
||||
|
||||
HistoryItem *ChannelHistory::addNewToBlocks(const MTPMessage &msg, NewMessageType type) {
|
||||
if (!loadedAtBottom()) {
|
||||
HistoryItem *item = addToHistory(msg);
|
||||
if (item) {
|
||||
setLastMessage(item);
|
||||
if (type == NewMessageUnread) {
|
||||
newItemAdded(item);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
return addNewToLastBlock(msg, type);
|
||||
}
|
||||
|
||||
void ChannelHistory::cleared(bool leaveItems) {
|
||||
_joinedMessage = nullptr;
|
||||
}
|
||||
@@ -678,7 +658,7 @@ void checkForSwitchInlineButton(HistoryItem *item) {
|
||||
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
for_const (auto &row, markup->rows) {
|
||||
for_const (auto &button, row) {
|
||||
if (button.type == HistoryMessageReplyMarkup::Button::Type::SwitchInline) {
|
||||
if (button.type == HistoryMessageMarkupButton::Type::SwitchInline) {
|
||||
Notify::switchInlineBotButtonReceived(QString::fromUtf8(button.data));
|
||||
return;
|
||||
}
|
||||
@@ -795,12 +775,7 @@ void Histories::checkSelfDestructItems() {
|
||||
}
|
||||
|
||||
HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) {
|
||||
auto msgId = MsgId(0);
|
||||
switch (msg.type()) {
|
||||
case mtpc_messageEmpty: msgId = msg.c_messageEmpty().vid.v; break;
|
||||
case mtpc_message: msgId = msg.c_message().vid.v; break;
|
||||
case mtpc_messageService: msgId = msg.c_messageService().vid.v; break;
|
||||
}
|
||||
const auto msgId = idFromMessage(msg);
|
||||
if (!msgId) return nullptr;
|
||||
|
||||
auto result = App::histItemById(channelId(), msgId);
|
||||
@@ -809,7 +784,10 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
|
||||
result->detach();
|
||||
}
|
||||
if (msg.type() == mtpc_message) {
|
||||
result->updateMedia(msg.c_message().has_media() ? (&msg.c_message().vmedia) : 0);
|
||||
const auto media = msg.c_message().has_media()
|
||||
? &msg.c_message().vmedia
|
||||
: nullptr;
|
||||
result->updateMedia(media);
|
||||
if (applyServiceAction) {
|
||||
App::checkSavedGif(result);
|
||||
}
|
||||
@@ -1093,72 +1071,85 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
|
||||
return result;
|
||||
}
|
||||
|
||||
HistoryItem *History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg) {
|
||||
not_null<HistoryItem*> History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg) {
|
||||
return HistoryMessage::create(this, id, flags, date, from, postAuthor, msg);
|
||||
}
|
||||
|
||||
HistoryItem *History::createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
not_null<HistoryItem*> History::createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, doc, caption, markup);
|
||||
}
|
||||
|
||||
HistoryItem *History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
not_null<HistoryItem*> History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, photo, caption, markup);
|
||||
}
|
||||
|
||||
HistoryItem *History::createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
||||
not_null<HistoryItem*> History::createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
||||
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, game, markup);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) {
|
||||
not_null<HistoryItem*> History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) {
|
||||
auto message = HistoryService::PreparedText { text };
|
||||
return addNewItem(HistoryService::create(this, msgId, date, message, flags), newMsg);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type) {
|
||||
if (isChannel()) return asChannelHistory()->addNewChannelMessage(msg, type);
|
||||
|
||||
if (type == NewMessageExisting) return addToHistory(msg);
|
||||
if (type == NewMessageExisting) {
|
||||
return addToHistory(msg);
|
||||
}
|
||||
if (!loadedAtBottom() || peer->migrateTo()) {
|
||||
HistoryItem *item = addToHistory(msg);
|
||||
if (item) {
|
||||
if (const auto item = addToHistory(msg)) {
|
||||
setLastMessage(item);
|
||||
if (type == NewMessageUnread) {
|
||||
newItemAdded(item);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
return item;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return addNewToLastBlock(msg, type);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewToLastBlock(const MTPMessage &msg, NewMessageType type) {
|
||||
auto applyServiceAction = (type == NewMessageUnread);
|
||||
auto detachExistingItem = (type != NewMessageLast);
|
||||
auto item = createItem(msg, applyServiceAction, detachExistingItem);
|
||||
Expects(type != NewMessageExisting);
|
||||
|
||||
const auto applyServiceAction = (type == NewMessageUnread);
|
||||
const auto detachExistingItem = (type != NewMessageLast);
|
||||
const auto item = createItem(msg, applyServiceAction, detachExistingItem);
|
||||
if (!item || !item->detached()) {
|
||||
return item;
|
||||
}
|
||||
return addNewItem(item, (type == NewMessageUnread));
|
||||
const auto result = addNewItem(item, (type == NewMessageUnread));
|
||||
if (type == NewMessageLast) {
|
||||
// When we add just one last item, like we do while loading dialogs,
|
||||
// we want to remove a single added grouped media, otherwise it will
|
||||
// jump once we open the message history (first we show only that
|
||||
// media, then we load the rest of the group and show the group).
|
||||
//
|
||||
// That way when we open the message history we show nothing until a
|
||||
// whole history part is loaded, it certainly will contain the group.
|
||||
removeOrphanMediaGroupPart();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
HistoryItem *History::addToHistory(const MTPMessage &msg) {
|
||||
return createItem(msg, false, false);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item) {
|
||||
not_null<HistoryItem*> History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item) {
|
||||
return addNewItem(createItemForwarded(id, flags, date, from, postAuthor, item), true);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
not_null<HistoryItem*> History::addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
return addNewItem(createItemDocument(id, flags, viaBotId, replyTo, date, from, postAuthor, doc, caption, markup), true);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
not_null<HistoryItem*> History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
return addNewItem(createItemPhoto(id, flags, viaBotId, replyTo, date, from, postAuthor, photo, caption, markup), true);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
||||
not_null<HistoryItem*> History::addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
||||
return addNewItem(createItemGame(id, flags, viaBotId, replyTo, date, from, postAuthor, game, markup), true);
|
||||
}
|
||||
|
||||
@@ -1172,15 +1163,17 @@ void History::setUnreadMentionsCount(int count) {
|
||||
|
||||
bool History::addToUnreadMentions(
|
||||
MsgId msgId,
|
||||
AddToUnreadMentionsMethod method) {
|
||||
auto allLoaded = _unreadMentionsCount ? (_unreadMentions.size() >= *_unreadMentionsCount) : false;
|
||||
UnreadMentionType type) {
|
||||
auto allLoaded = _unreadMentionsCount
|
||||
? (_unreadMentions.size() >= *_unreadMentionsCount)
|
||||
: false;
|
||||
if (allLoaded) {
|
||||
if (method == AddToUnreadMentionsMethod::New) {
|
||||
if (type == UnreadMentionType::New) {
|
||||
++*_unreadMentionsCount;
|
||||
_unreadMentions.insert(msgId);
|
||||
return true;
|
||||
}
|
||||
} else if (!_unreadMentions.empty() && method != AddToUnreadMentionsMethod::New) {
|
||||
} else if (!_unreadMentions.empty() && type != UnreadMentionType::New) {
|
||||
_unreadMentions.insert(msgId);
|
||||
return true;
|
||||
}
|
||||
@@ -1250,32 +1243,24 @@ void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) {
|
||||
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged);
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
|
||||
not_null<HistoryItem*> History::addNewItem(not_null<HistoryItem*> adding, bool newMsg) {
|
||||
Expects(!isBuildingFrontBlock());
|
||||
|
||||
addItemToBlock(adding);
|
||||
|
||||
setLastMessage(adding);
|
||||
if (newMsg) {
|
||||
newItemAdded(adding);
|
||||
const auto [groupFrom, groupTill] = recountGroupingFromTill(adding);
|
||||
if (groupFrom != groupTill || groupFrom->groupId()) {
|
||||
recountGrouping(groupFrom, groupTill);
|
||||
}
|
||||
|
||||
adding->addToUnreadMentions(AddToUnreadMentionsMethod::New);
|
||||
if (IsServerMsgId(adding->id)) {
|
||||
if (auto sharedMediaTypes = adding->sharedMediaTypes()) {
|
||||
if (newMsg) {
|
||||
Auth().storage().add(Storage::SharedMediaAddNew(
|
||||
peer->id,
|
||||
sharedMediaTypes,
|
||||
adding->id));
|
||||
} else {
|
||||
auto from = loadedAtTop() ? 0 : minMsgId();
|
||||
auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
|
||||
Auth().storage().add(Storage::SharedMediaAddExisting(
|
||||
peer->id,
|
||||
sharedMediaTypes,
|
||||
adding->id,
|
||||
{ from, till }));
|
||||
}
|
||||
if (!newMsg && IsServerMsgId(adding->id)) {
|
||||
if (const auto sharedMediaTypes = adding->sharedMediaTypes()) {
|
||||
auto from = loadedAtTop() ? 0 : minMsgId();
|
||||
auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
|
||||
Auth().storage().add(Storage::SharedMediaAddExisting(
|
||||
peer->id,
|
||||
sharedMediaTypes,
|
||||
adding->id,
|
||||
{ from, till }));
|
||||
}
|
||||
}
|
||||
if (adding->from()->id) {
|
||||
@@ -1358,6 +1343,11 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
|
||||
}
|
||||
}
|
||||
|
||||
setLastMessage(adding);
|
||||
if (newMsg) {
|
||||
newItemAdded(adding);
|
||||
}
|
||||
|
||||
return adding;
|
||||
}
|
||||
|
||||
@@ -1378,8 +1368,9 @@ void History::clearSendAction(not_null<UserData*> from) {
|
||||
}
|
||||
}
|
||||
|
||||
void History::newItemAdded(HistoryItem *item) {
|
||||
void History::newItemAdded(not_null<HistoryItem*> item) {
|
||||
App::checkImageCacheSize();
|
||||
item->indexAsNewItem();
|
||||
if (const auto from = item->from() ? item->from()->asUser() : nullptr) {
|
||||
if (from == item->author()) {
|
||||
clearSendAction(from);
|
||||
@@ -1388,7 +1379,9 @@ void History::newItemAdded(HistoryItem *item) {
|
||||
from->madeAction(itemServerTime.v);
|
||||
}
|
||||
if (item->out()) {
|
||||
if (unreadBar) unreadBar->destroyUnreadBar();
|
||||
if (unreadBar) {
|
||||
unreadBar->destroyUnreadBar();
|
||||
}
|
||||
if (!item->unread()) {
|
||||
outboxRead(item);
|
||||
}
|
||||
@@ -1433,8 +1426,7 @@ HistoryBlock *History::prepareBlockForAddingItem() {
|
||||
return result;
|
||||
};
|
||||
|
||||
void History::addItemToBlock(HistoryItem *item) {
|
||||
Expects(item != nullptr);
|
||||
void History::addItemToBlock(not_null<HistoryItem*> item) {
|
||||
Expects(item->detached());
|
||||
|
||||
auto block = prepareBlockForAddingItem();
|
||||
@@ -1499,7 +1491,7 @@ void History::addToSharedMedia(std::vector<MsgId> (&medias)[kSharedMediaTypeCoun
|
||||
}
|
||||
value.push_back(indices.join(","));
|
||||
}
|
||||
SignalHandlers::setCrashAnnotation("full", value.join(";"));
|
||||
CrashReports::SetAnnotation("full", value.join(";"));
|
||||
Assert(!"History desync caught!");
|
||||
//// Logging
|
||||
|
||||
@@ -1527,6 +1519,9 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto firstAdded = (HistoryItem*)nullptr;
|
||||
auto lastAdded = (HistoryItem*)nullptr;
|
||||
|
||||
auto logged = QStringList();
|
||||
logged.push_back(QString::number(minMsgId()));
|
||||
logged.push_back(QString::number(maxMsgId()));
|
||||
@@ -1538,9 +1533,12 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||
|
||||
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
|
||||
--i;
|
||||
auto adding = createItem(*i, false, true);
|
||||
const auto adding = createItem(*i, false, true);
|
||||
if (!adding) continue;
|
||||
|
||||
if (!firstAdded) firstAdded = adding;
|
||||
lastAdded = adding;
|
||||
|
||||
if (minAdded < 0 || minAdded > adding->id) {
|
||||
minAdded = adding->id;
|
||||
}
|
||||
@@ -1573,7 +1571,7 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||
}
|
||||
for (auto i = block->items.size(); i > 0; --i) {
|
||||
auto item = block->items[i - 1];
|
||||
item->addToUnreadMentions(AddToUnreadMentionsMethod::Front);
|
||||
item->addToUnreadMentions(UnreadMentionType::Existing);
|
||||
if (item->from()->id) {
|
||||
if (lastAuthors) { // chats
|
||||
if (auto user = item->from()->asUser()) {
|
||||
@@ -1631,11 +1629,16 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||
|
||||
logged.push_back(QString::number(minAdded));
|
||||
logged.push_back(QString::number(maxAdded));
|
||||
SignalHandlers::setCrashAnnotation("old_minmaxwas_minmaxadd", logged.join(";"));
|
||||
CrashReports::SetAnnotation("old_minmaxwas_minmaxadd", logged.join(";"));
|
||||
|
||||
addBlockToSharedMedia(block);
|
||||
|
||||
SignalHandlers::setCrashAnnotation("old_minmaxwas_minmaxadd", "");
|
||||
CrashReports::ClearAnnotation("old_minmaxwas_minmaxadd");
|
||||
|
||||
if (lastAdded) {
|
||||
const auto [from, till] = recountGroupingFromTill(lastAdded);
|
||||
recountGrouping(firstAdded, till);
|
||||
}
|
||||
|
||||
if (isChannel()) {
|
||||
asChannelHistory()->checkJoinedMessage();
|
||||
@@ -1654,6 +1657,9 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||
}
|
||||
}
|
||||
|
||||
auto firstAdded = (HistoryItem*)nullptr;
|
||||
auto lastAdded = (HistoryItem*)nullptr;
|
||||
|
||||
Assert(!isBuildingFrontBlock());
|
||||
if (!slice.isEmpty()) {
|
||||
auto logged = QStringList();
|
||||
@@ -1664,12 +1670,14 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||
auto maxAdded = -1;
|
||||
|
||||
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
|
||||
auto atLeastOneAdded = false;
|
||||
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
|
||||
--i;
|
||||
auto adding = createItem(*i, false, true);
|
||||
const auto adding = createItem(*i, false, true);
|
||||
if (!adding) continue;
|
||||
|
||||
if (!firstAdded) firstAdded = adding;
|
||||
lastAdded = adding;
|
||||
|
||||
if (minAdded < 0 || minAdded > adding->id) {
|
||||
minAdded = adding->id;
|
||||
}
|
||||
@@ -1678,7 +1686,6 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||
}
|
||||
|
||||
addItemToBlock(adding);
|
||||
atLeastOneAdded = true;
|
||||
if (auto types = adding->sharedMediaTypes()) {
|
||||
for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
|
||||
auto type = static_cast<Storage::SharedMediaType>(i);
|
||||
@@ -1693,21 +1700,26 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||
}
|
||||
logged.push_back(QString::number(minAdded));
|
||||
logged.push_back(QString::number(maxAdded));
|
||||
SignalHandlers::setCrashAnnotation("new_minmaxwas_minmaxadd", logged.join(";"));
|
||||
CrashReports::SetAnnotation("new_minmaxwas_minmaxadd", logged.join(";"));
|
||||
|
||||
if (!atLeastOneAdded) {
|
||||
if (!firstAdded) {
|
||||
newLoaded = true;
|
||||
setLastMessage(lastAvailableMessage());
|
||||
}
|
||||
addToSharedMedia(medias, wasLoadedAtBottom != loadedAtBottom());
|
||||
|
||||
SignalHandlers::setCrashAnnotation("new_minmaxwas_minmaxadd", "");
|
||||
CrashReports::ClearAnnotation("new_minmaxwas_minmaxadd");
|
||||
}
|
||||
|
||||
if (!wasLoadedAtBottom) {
|
||||
checkAddAllToUnreadMentions();
|
||||
}
|
||||
|
||||
if (firstAdded) {
|
||||
const auto [from, till] = recountGroupingFromTill(firstAdded);
|
||||
recountGrouping(from, lastAdded);
|
||||
}
|
||||
|
||||
if (isChannel()) asChannelHistory()->checkJoinedMessage();
|
||||
checkLastMsg();
|
||||
}
|
||||
@@ -1728,9 +1740,9 @@ void History::checkAddAllToUnreadMentions() {
|
||||
return;
|
||||
}
|
||||
|
||||
for_const (auto block, blocks) {
|
||||
for_const (auto item, block->items) {
|
||||
item->addToUnreadMentions(AddToUnreadMentionsMethod::Back);
|
||||
for (const auto block : blocks) {
|
||||
for (const auto item : block->items) {
|
||||
item->addToUnreadMentions(UnreadMentionType::Existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2006,7 +2018,7 @@ void History::destroyUnreadBar() {
|
||||
}
|
||||
}
|
||||
|
||||
HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex) {
|
||||
not_null<HistoryItem*> History::addNewInTheMiddle(not_null<HistoryItem*> newItem, int32 blockIndex, int32 itemIndex) {
|
||||
Expects(blockIndex >= 0);
|
||||
Expects(blockIndex < blocks.size());
|
||||
Expects(itemIndex >= 0);
|
||||
@@ -2028,9 +2040,135 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex,
|
||||
newItem->nextItemChanged();
|
||||
}
|
||||
|
||||
const auto [groupFrom, groupTill] = recountGroupingFromTill(newItem);
|
||||
if (groupFrom != groupTill || groupFrom->groupId()) {
|
||||
recountGrouping(groupFrom, groupTill);
|
||||
}
|
||||
|
||||
return newItem;
|
||||
}
|
||||
|
||||
HistoryItem *History::findNextItem(not_null<HistoryItem*> item) const {
|
||||
Expects(!item->detached());
|
||||
|
||||
const auto nextBlockIndex = item->block()->indexInHistory() + 1;
|
||||
const auto nextItemIndex = item->indexInBlock() + 1;
|
||||
if (nextItemIndex < int(item->block()->items.size())) {
|
||||
return item->block()->items[nextItemIndex];
|
||||
} else if (nextBlockIndex < int(blocks.size())) {
|
||||
return blocks[nextBlockIndex]->items.front();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HistoryItem *History::findPreviousItem(not_null<HistoryItem*> item) const {
|
||||
Expects(!item->detached());
|
||||
|
||||
const auto blockIndex = item->block()->indexInHistory();
|
||||
const auto itemIndex = item->indexInBlock();
|
||||
if (itemIndex > 0) {
|
||||
return item->block()->items[itemIndex - 1];
|
||||
} else if (blockIndex > 0) {
|
||||
return blocks[blockIndex - 1]->items.back();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> History::findGroupFirst(
|
||||
not_null<HistoryItem*> item) const {
|
||||
const auto group = item->Get<HistoryMessageGroup>();
|
||||
Assert(group != nullptr);
|
||||
Assert(group->leader != nullptr);
|
||||
|
||||
const auto leaderGroup = (group->leader == item)
|
||||
? group
|
||||
: group->leader->Get<HistoryMessageGroup>();
|
||||
Assert(leaderGroup != nullptr);
|
||||
|
||||
return leaderGroup->others.empty()
|
||||
? group->leader
|
||||
: leaderGroup->others.front().get();
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> History::findGroupLast(
|
||||
not_null<HistoryItem*> item) const {
|
||||
const auto group = item->Get<HistoryMessageGroup>();
|
||||
Assert(group != nullptr);
|
||||
|
||||
return group->leader;
|
||||
}
|
||||
|
||||
void History::recountGroupingAround(not_null<HistoryItem*> item) {
|
||||
Expects(item->history() == this);
|
||||
|
||||
if (!item->detached() && item->groupId()) {
|
||||
const auto [groupFrom, groupTill] = recountGroupingFromTill(item);
|
||||
recountGrouping(groupFrom, groupTill);
|
||||
}
|
||||
}
|
||||
|
||||
auto History::recountGroupingFromTill(not_null<HistoryItem*> item)
|
||||
-> std::pair<not_null<HistoryItem*>, not_null<HistoryItem*>> {
|
||||
const auto recountFromItem = [&] {
|
||||
if (const auto prev = findPreviousItem(item)) {
|
||||
if (prev->groupId()) {
|
||||
return findGroupFirst(prev);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}();
|
||||
if (recountFromItem == item && !item->groupId()) {
|
||||
return { item, item };
|
||||
}
|
||||
const auto recountTillItem = [&] {
|
||||
if (const auto next = findNextItem(item)) {
|
||||
if (next->groupId()) {
|
||||
return findGroupLast(next);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}();
|
||||
return { recountFromItem, recountTillItem };
|
||||
}
|
||||
|
||||
void History::recountGrouping(
|
||||
not_null<HistoryItem*> from,
|
||||
not_null<HistoryItem*> till) {
|
||||
Expects(!from->detached());
|
||||
Expects(!till->detached());
|
||||
|
||||
from->validateGroupId();
|
||||
auto others = std::vector<not_null<HistoryItem*>>();
|
||||
auto currentGroupId = from->groupId();
|
||||
auto prev = from;
|
||||
while (prev != till) {
|
||||
auto item = findNextItem(prev);
|
||||
item->validateGroupId();
|
||||
const auto groupId = item->groupId();
|
||||
if (currentGroupId) {
|
||||
if (groupId == currentGroupId) {
|
||||
others.push_back(prev);
|
||||
} else {
|
||||
for (const auto other : others) {
|
||||
other->makeGroupMember(prev);
|
||||
}
|
||||
prev->makeGroupLeader(base::take(others));
|
||||
currentGroupId = groupId;
|
||||
}
|
||||
} else if (groupId) {
|
||||
currentGroupId = groupId;
|
||||
}
|
||||
prev = item;
|
||||
}
|
||||
|
||||
if (currentGroupId) {
|
||||
for (const auto other : others) {
|
||||
other->makeGroupMember(prev);
|
||||
}
|
||||
till->makeGroupLeader(base::take(others));
|
||||
}
|
||||
}
|
||||
|
||||
void History::startBuildingFrontBlock(int expectedItemsCount) {
|
||||
Assert(!isBuildingFrontBlock());
|
||||
Assert(expectedItemsCount > 0);
|
||||
@@ -2251,11 +2389,11 @@ int History::resizeGetHeight(int newWidth) {
|
||||
}
|
||||
|
||||
ChannelHistory *History::asChannelHistory() {
|
||||
return isChannel() ? static_cast<ChannelHistory*>(this) : 0;
|
||||
return isChannel() ? static_cast<ChannelHistory*>(this) : nullptr;
|
||||
}
|
||||
|
||||
const ChannelHistory *History::asChannelHistory() const {
|
||||
return isChannel() ? static_cast<const ChannelHistory*>(this) : 0;
|
||||
return isChannel() ? static_cast<const ChannelHistory*>(this) : nullptr;
|
||||
}
|
||||
|
||||
not_null<History*> History::migrateToOrMe() const {
|
||||
@@ -2277,6 +2415,25 @@ bool History::isDisplayedEmpty() const {
|
||||
return isEmpty() || ((blocks.size() == 1) && blocks.front()->items.size() == 1 && blocks.front()->items.front()->isEmpty());
|
||||
}
|
||||
|
||||
bool History::hasOrphanMediaGroupPart() const {
|
||||
if (loadedAtTop() || !loadedAtBottom()) {
|
||||
return false;
|
||||
} else if (blocks.size() != 1) {
|
||||
return false;
|
||||
} else if (blocks.front()->items.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
return blocks.front()->items.front()->groupId() != MessageGroupId();
|
||||
}
|
||||
|
||||
bool History::removeOrphanMediaGroupPart() {
|
||||
if (hasOrphanMediaGroupPart()) {
|
||||
clear(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void History::clear(bool leaveItems) {
|
||||
if (unreadBar) {
|
||||
unreadBar = nullptr;
|
||||
@@ -2470,7 +2627,7 @@ void History::setPinnedIndex(int pinnedIndex) {
|
||||
void History::changeMsgId(MsgId oldId, MsgId newId) {
|
||||
}
|
||||
|
||||
void History::removeBlock(HistoryBlock *block) {
|
||||
void History::removeBlock(not_null<HistoryBlock*> block) {
|
||||
Expects(block->items.empty());
|
||||
|
||||
if (_buildingFrontBlock && block == _buildingFrontBlock->block) {
|
||||
@@ -2521,9 +2678,21 @@ void HistoryBlock::clear(bool leaveItems) {
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryBlock::removeItem(HistoryItem *item) {
|
||||
void HistoryBlock::removeItem(not_null<HistoryItem*> item) {
|
||||
Expects(item->block() == this);
|
||||
|
||||
auto [groupFrom, groupTill] = _history->recountGroupingFromTill(item);
|
||||
const auto groupHistory = _history;
|
||||
const auto needGroupRecount = (groupFrom != groupTill);
|
||||
if (needGroupRecount) {
|
||||
if (groupFrom == item) {
|
||||
groupFrom = groupHistory->findNextItem(groupFrom);
|
||||
}
|
||||
if (groupTill == item) {
|
||||
groupTill = groupHistory->findPreviousItem(groupTill);
|
||||
}
|
||||
}
|
||||
|
||||
auto blockIndex = indexInHistory();
|
||||
auto itemIndex = item->indexInBlock();
|
||||
if (_history->showFrom == item) {
|
||||
@@ -2557,4 +2726,8 @@ void HistoryBlock::removeItem(HistoryItem *item) {
|
||||
if (items.empty()) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
if (needGroupRecount) {
|
||||
groupHistory->recountGrouping(groupFrom, groupTill);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "base/flat_set.h"
|
||||
#include "base/flags.h"
|
||||
|
||||
void HistoryInit();
|
||||
|
||||
class HistoryItem;
|
||||
using HistoryItemsList = std::vector<not_null<HistoryItem*>>;
|
||||
|
||||
@@ -136,6 +134,7 @@ enum HistoryMediaType {
|
||||
MediaTypeVoiceFile,
|
||||
MediaTypeGame,
|
||||
MediaTypeInvoice,
|
||||
MediaTypeGrouped,
|
||||
|
||||
MediaTypeCount
|
||||
};
|
||||
@@ -172,10 +171,9 @@ struct Draft;
|
||||
class HistoryMedia;
|
||||
class HistoryMessage;
|
||||
|
||||
enum class AddToUnreadMentionsMethod {
|
||||
enum class UnreadMentionType {
|
||||
New, // when new message is added to history
|
||||
Front, // when old messages slice was received
|
||||
Back, // when new messages slice was received and it is the last one, we index all media
|
||||
Existing, // when some messages slice was received
|
||||
};
|
||||
|
||||
namespace Dialogs {
|
||||
@@ -209,6 +207,8 @@ public:
|
||||
return blocks.empty();
|
||||
}
|
||||
bool isDisplayedEmpty() const;
|
||||
bool hasOrphanMediaGroupPart() const;
|
||||
bool removeOrphanMediaGroupPart();
|
||||
|
||||
void clear(bool leaveItems = false);
|
||||
void clearUpTill(MsgId availableMinId);
|
||||
@@ -217,13 +217,13 @@ public:
|
||||
|
||||
virtual ~History();
|
||||
|
||||
HistoryItem *addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
|
||||
HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type);
|
||||
HistoryItem *addToHistory(const MTPMessage &msg);
|
||||
HistoryItem *addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
|
||||
HistoryItem *addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
|
||||
HistoryItem *addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
|
||||
HistoryItem *addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
|
||||
not_null<HistoryItem*> addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
|
||||
not_null<HistoryItem*> addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
|
||||
not_null<HistoryItem*> addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
|
||||
not_null<HistoryItem*> addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
|
||||
not_null<HistoryItem*> addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
|
||||
|
||||
// Used only internally and for channel admin log.
|
||||
HistoryItem *createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem);
|
||||
@@ -231,7 +231,7 @@ public:
|
||||
void addOlderSlice(const QVector<MTPMessage> &slice);
|
||||
void addNewerSlice(const QVector<MTPMessage> &slice);
|
||||
|
||||
void newItemAdded(HistoryItem *item);
|
||||
void newItemAdded(not_null<HistoryItem*> item);
|
||||
|
||||
int countUnread(MsgId upTo);
|
||||
void updateShowFrom();
|
||||
@@ -366,7 +366,7 @@ public:
|
||||
return (getUnreadMentionsCount() > 0);
|
||||
}
|
||||
void setUnreadMentionsCount(int count);
|
||||
bool addToUnreadMentions(MsgId msgId, AddToUnreadMentionsMethod method);
|
||||
bool addToUnreadMentions(MsgId msgId, UnreadMentionType type);
|
||||
void eraseFromUnreadMentions(MsgId msgId);
|
||||
void addUnreadMentionsSlice(const MTPmessages_Messages &result);
|
||||
|
||||
@@ -419,6 +419,7 @@ public:
|
||||
}
|
||||
HistoryItemsList validateForwardDraft();
|
||||
void setForwardDraft(MessageIdsList &&items);
|
||||
void recountGroupingAround(not_null<HistoryItem*> item);
|
||||
|
||||
// some fields below are a property of a currently displayed instance of this
|
||||
// conversation history not a property of the conversation history itself
|
||||
@@ -475,17 +476,17 @@ protected:
|
||||
// this method just removes a block from the blocks list
|
||||
// when the last item from this block was detached and
|
||||
// calls the required previousItemChanged()
|
||||
void removeBlock(HistoryBlock *block);
|
||||
void removeBlock(not_null<HistoryBlock*> block);
|
||||
|
||||
void clearBlocks(bool leaveItems);
|
||||
|
||||
HistoryItem *createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg);
|
||||
HistoryItem *createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
|
||||
HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
|
||||
HistoryItem *createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
|
||||
not_null<HistoryItem*> createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg);
|
||||
not_null<HistoryItem*> createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
|
||||
not_null<HistoryItem*> createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
|
||||
not_null<HistoryItem*> createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
|
||||
|
||||
HistoryItem *addNewItem(HistoryItem *adding, bool newMsg);
|
||||
HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex);
|
||||
not_null<HistoryItem*> addNewItem(not_null<HistoryItem*> adding, bool newMsg);
|
||||
not_null<HistoryItem*> addNewInTheMiddle(not_null<HistoryItem*> newItem, int32 blockIndex, int32 itemIndex);
|
||||
|
||||
// All this methods add a new item to the first or last block
|
||||
// depending on if we are in isBuildingFronBlock() state.
|
||||
@@ -493,7 +494,7 @@ protected:
|
||||
|
||||
// Adds the item to the back or front block, depending on
|
||||
// isBuildingFrontBlock(), creating the block if necessary.
|
||||
void addItemToBlock(HistoryItem *item);
|
||||
void addItemToBlock(not_null<HistoryItem*> item);
|
||||
|
||||
// Usually all new items are added to the last block.
|
||||
// Only when we scroll up and add a new slice to the
|
||||
@@ -517,6 +518,18 @@ private:
|
||||
|
||||
void clearSendAction(not_null<UserData*> from);
|
||||
|
||||
HistoryItem *findPreviousItem(not_null<HistoryItem*> item) const;
|
||||
HistoryItem *findNextItem(not_null<HistoryItem*> item) const;
|
||||
not_null<HistoryItem*> findGroupFirst(
|
||||
not_null<HistoryItem*> item) const;
|
||||
not_null<HistoryItem*> findGroupLast(
|
||||
not_null<HistoryItem*> item) const;
|
||||
auto recountGroupingFromTill(not_null<HistoryItem*> item)
|
||||
-> std::pair<not_null<HistoryItem*>, not_null<HistoryItem*>>;
|
||||
void recountGrouping(
|
||||
not_null<HistoryItem*> from,
|
||||
not_null<HistoryItem*> till);
|
||||
|
||||
enum class Flag {
|
||||
f_has_pending_resized_items = (1 << 0),
|
||||
f_pending_resize = (1 << 1),
|
||||
@@ -593,11 +606,8 @@ public:
|
||||
|
||||
private:
|
||||
friend class History;
|
||||
HistoryItem* addNewChannelMessage(const MTPMessage &msg, NewMessageType type);
|
||||
HistoryItem *addNewToBlocks(const MTPMessage &msg, NewMessageType type);
|
||||
|
||||
void checkMaxReadMessageDate();
|
||||
|
||||
void cleared(bool leaveItems);
|
||||
|
||||
QDateTime _maxReadMessageDate;
|
||||
@@ -624,7 +634,7 @@ public:
|
||||
~HistoryBlock() {
|
||||
clear();
|
||||
}
|
||||
void removeItem(HistoryItem *item);
|
||||
void removeItem(not_null<HistoryItem*> item);
|
||||
|
||||
int resizeGetHeight(int newWidth, bool resizeAllItems);
|
||||
int y() const {
|
||||
|
||||
@@ -123,6 +123,8 @@ historyFileThumbCancel: icon {{ "history_file_cancel", historyFileThumbIconFg }}
|
||||
historyFileThumbCancelSelected: icon {{ "history_file_cancel", historyFileThumbIconFgSelected }};
|
||||
historyFileThumbPlay: icon {{ "history_file_play", historyFileThumbIconFg }};
|
||||
historyFileThumbPlaySelected: icon {{ "history_file_play", historyFileThumbIconFgSelected }};
|
||||
historyFileThumbWaiting: icon {{ "mediaview_save_check", historyFileThumbIconFg }};
|
||||
historyFileThumbWaitingSelected: icon {{ "mediaview_save_check", historyFileThumbIconFgSelected }};
|
||||
|
||||
historySendStateSpace: 24px;
|
||||
historySendStatePosition: point(-17px, -19px);
|
||||
@@ -461,3 +463,9 @@ historyFastShareIcon: icon {{ "fast_share", msgServiceFg, point(4px, 3px)}};
|
||||
historyGoToOriginalIcon: icon {{ "title_back-flip_horizontal", msgServiceFg, point(8px, 7px) }};
|
||||
|
||||
historySavedFont: font(semibold 14px);
|
||||
|
||||
historyGroupWidthMax: maxMediaSize;
|
||||
historyGroupWidthMin: minPhotoSize;
|
||||
historyGroupSkip: 4px;
|
||||
historyGroupRadialSize: 44px;
|
||||
historyGroupRadialLine: 3px;
|
||||
|
||||
@@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_peer_values.h"
|
||||
|
||||
namespace AdminLog {
|
||||
namespace {
|
||||
@@ -79,8 +80,8 @@ UserCheckbox::UserCheckbox(QWidget *parent, not_null<UserData*> user, bool check
|
||||
setChecked(!this->checked());
|
||||
});
|
||||
auto now = unixtime();
|
||||
_statusText = App::onlineText(_user, now);
|
||||
_statusOnline = App::onlineColorUse(_user, now);
|
||||
_statusText = Data::OnlineText(_user, now);
|
||||
_statusOnline = Data::OnlineTextActive(_user, now);
|
||||
auto checkSize = _check->getSize();
|
||||
_checkRect = { QPoint(_st.margin.left(), (st::contactsPhotoSize - checkSize.height()) / 2), checkSize };
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user