Compare commits

...

68 Commits

Author SHA1 Message Date
John Preston
26e023058c Version 1.2.6.
- Grouped Photos. Group media into an album when sharing multiple photos and videos.
Choose the exact order of media you send.
2017-12-30 13:13:48 +03:00
John Preston
6236590ca4 Fix SendFilesWay radiobuttons when adding media.
Also add /LTCG flag for static libraries Release builds on Windows.
2017-12-30 00:06:43 +03:00
John Preston
ea51f976f2 Alpha version 1.2.5: Workaround GCC 7.2 ICE. 2017-12-29 21:47:49 +03:00
John Preston
719f3428ec Alpha version 1.2.5:
- 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.
- When composing an album paste additional media from the clipboard.
- Bug fixes and other minor improvements.
2017-12-29 21:21:57 +03:00
John Preston
2df4d19474 Move changelogs from ApiWrap to a separate module. 2017-12-29 21:17:07 +03:00
John Preston
2a409e3734 Add files from clipboard to composed album.
Fixes #4243.
2017-12-29 20:02:23 +03:00
John Preston
0171a4e874 Handle click on group thumb item in MediaView. 2017-12-29 17:58:53 +03:00
John Preston
59e5ffe743 Don't insert mime text in field for url list.
Fixes #4241.
2017-12-29 17:58:32 +03:00
John Preston
2bcbb5a5be Display group / userpic thumbnails in MediaView. 2017-12-29 16:44:36 +03:00
John Preston
5b4694a4eb Move text options constant to a separate module.
Also start MediaView group thumbs code.
2017-12-28 16:06:06 +03:00
John Preston
54d6673d0b Display photos and videos together in MediaView. 2017-12-28 13:12:07 +03:00
John Preston
e07a7a4b4c Improve phrases. No Restricted Users in channels info. 2017-12-27 22:44:04 +03:00
John Preston
f2d11e7432 Fix video grouped thumb on Retina displays. 2017-12-27 22:16:26 +03:00
John Preston
1a115cc7e5 Fix file upload progress display.
Regression was introduced in 5d18d7c813.
2017-12-27 21:57:37 +03:00
John Preston
a00941f7ce Update crl and libtgvoip submodules. 2017-12-27 21:49:08 +03:00
John Preston
634d21e486 Fix animation in album reordering. 2017-12-27 14:00:32 +03:00
John Preston
95d8742e3c Fix round corners on Retina displays. 2017-12-27 13:08:18 +03:00
John Preston
bd8dee0972 Fix crash in audio player hiding. 2017-12-27 10:18:09 +03:00
John Preston
b34099f49e Alpha version 1.2.4: Fix phrases and box cursor. 2017-12-26 20:49:04 +03:00
John Preston
0380e66c30 Alpha version 1.2.4: Fix build for old OS X versions. 2017-12-26 19:53:51 +03:00
John Preston
907b6f0a78 Alpha version 1.2.4.
- Group media into an album when sharing multiple photos and videos.
- Bug fixes and other minor improvements.
2017-12-26 19:20:08 +03:00
John Preston
21f4bbbe7b Update kicked count in Channel Info box. 2017-12-26 19:20:08 +03:00
John Preston
69d9072ff0 Allow fast admin removing in supergroups/channels. 2017-12-26 19:20:08 +03:00
John Preston
d5ae9bcba2 Close box when showing a layer section from it. 2017-12-26 19:20:08 +03:00
John Preston
86c0dfb295 Read "participants_count" field from c_channel(). 2017-12-26 19:20:08 +03:00
John Preston
01821c30e5 Fix message field focus loss in Saved Messages. 2017-12-26 19:20:08 +03:00
John Preston
7f66e0fdfe Hide remove member button for supegroup admins. 2017-12-26 19:20:08 +03:00
John Preston
2569df9e5a Rename some Ui methods.
myEnsureResized -> Ui::SendPendingMoveResizeEvents.
myGrab -> Ui::GrabWidget.
myGrabImage -> Ui::GrabWidgetToImage.
2017-12-26 19:20:08 +03:00
John Preston
5f8143e6a4 Fix build in Xcode. 2017-12-26 19:20:08 +03:00
John Preston
1fc7dabd3e Allow media reordering when sending an album. 2017-12-26 19:20:08 +03:00
John Preston
5d18d7c813 Send album after cancel of some media uploads.
Also display checks when part of the album medias are uploaded.
2017-12-26 19:20:07 +03:00
John Preston
4e8f5541af Fix caption editing in grouped media. 2017-12-26 19:20:07 +03:00
John Preston
a8ac18e4fd Save send way (album, photos, files) to settings. 2017-12-26 19:20:07 +03:00
John Preston
a6c15217c0 Fix grouped layout algorithm to match other apps. 2017-12-26 19:20:07 +03:00
John Preston
57351dd42a Remove QTextLayout that appears to be not needed. 2017-12-26 19:20:07 +03:00
John Preston
58d21ff916 Add album support to SendFilesBox. 2017-12-26 19:20:07 +03:00
John Preston
8e45b09083 Use different indentation for rpl operators.
It works better with Visual Studio IDE.
2017-12-26 19:20:07 +03:00
John Preston
44014e62ba Move EditCaptionBox to a separate module. 2017-12-26 19:20:06 +03:00
John Preston
ff65daa9fe Remove special case in SendFilesBox for an image. 2017-12-26 19:20:06 +03:00
John Preston
ec515080b5 Improve saved messages search results display. 2017-12-26 19:20:06 +03:00
John Preston
255dbf9405 Fix indexing of shared media.
Add new messages to shared media index even if !loadedAtBottom().
2017-12-26 19:20:06 +03:00
John Preston
aebb40dc1e Fix volume control disappearing in audio player. 2017-12-26 19:20:06 +03:00
John Preston
b20c2b4774 Always allow group admins to edit invite link.
Show Group Info even if supergroup admin can't edit information.
2017-12-26 19:20:06 +03:00
John Preston
3b3a705a67 First working code for sending albums. 2017-12-26 19:20:06 +03:00
Friedrich von Never
711aa51046 Fix handling of dashes in the style directory path
Now Telegram Desktop could be built if a path to the source directory
contains dashes.
2017-12-26 19:16:11 +03:00
John Preston
4d54cf1370 Update crl submodule. 2017-12-19 10:33:47 +04:00
John Preston
e023092744 Use RectPart(s) instead of ImageRoundCorner(s). 2017-12-18 21:54:11 +04:00
John Preston
2e421e8aed Allow unicode quotes as markdown entry separators.
Fixes #3867.
2017-12-18 21:06:44 +04:00
John Preston
afe9d38c48 Don't ruin links by markdown parsing.
Fixes #3851.
2017-12-18 20:49:40 +04:00
John Preston
3f751bfbb0 Activate window on dropEvent. 2017-12-18 20:25:24 +04:00
John Preston
b1f33890d6 Workaround GCC segmentation fault. 2017-12-18 19:52:58 +04:00
John Preston
92333e982c Move message components to history_item_components.
Also fix channel signatures rendering.
2017-12-18 19:52:36 +04:00
John Preston
16ca2d39c5 Fix _height value in inline bot result Gif layout.
This fixes render glitches in GIFs column with opened MediaView.
2017-12-18 18:10:24 +04:00
John Preston
977dee3599 Fix fast sharing from channels.
Grouped flag is only allowed for grouped media in forward requests.

Fixes #4198.
2017-12-18 17:29:48 +04:00
John Preston
546766fb13 Update FullMsgId context in HistoryMedia links. 2017-12-18 17:13:41 +04:00
John Preston
ddf4a36bdc Remove mtproto/session.h from precompiled header. 2017-12-18 16:40:15 +04:00
John Preston
fa3a76b3d8 Fix layout bug in grouped media rendering.
Also remove st::mediaPadding.
2017-12-18 15:40:43 +04:00
John Preston
d5de064019 Shuffle code around a bit.
Crash reports point to addToUnreadMentions() call being corrupted.
New reports could show is it responsible or setLastMessage() call.
2017-12-18 15:17:58 +04:00
John Preston
37b018257e Replace some std::shared_ptr with std::unique_ptr. 2017-12-18 14:38:14 +04:00
John Preston
14034c255e Replace QSharedPointer with std::shared_ptr. 2017-12-18 13:07:18 +04:00
John Preston
cbbccd0364 Hide history visibility edit for public groups. 2017-12-18 10:18:51 +04:00
John Preston
b8204a317d Testing crl (concurrency runtime library). 2017-12-17 23:05:00 +04:00
John Preston
499e3113b9 Allow HistoryGroupedMedia cloning.
We use it for local forwarded message creation, it should be main().
2017-12-17 17:01:34 +04:00
John Preston
656e4869e6 Move UnreadBadge to ui/unread_badge. 2017-12-17 15:04:47 +04:00
John Preston
defec611e3 Alpha version 1.2.3.
- Several crash fixes.
2017-12-17 12:41:35 +04:00
John Preston
49def354bd Fix bug causing crash in group recounting. 2017-12-17 12:33:08 +04:00
John Preston
712b3f481c Move online phrase code from app module.
Also fix possible assertion violation in online change timeout.
2017-12-17 12:13:26 +04:00
John Preston
b3a723c871 Fix crash in message history context menu.
Regression was introduced in 6d48ca850e.
2017-12-17 11:25:02 +04:00
249 changed files with 11936 additions and 6447 deletions

3
.gitmodules vendored
View File

@@ -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

View File

@@ -196,7 +196,6 @@ outSemiboldPalette: TextPalette(outTextPalette) {
selectLinkFg: msgOutServiceFgSelected;
}
mediaPadding: margins(0px, 0px, 0px, 0px);
mediaCaptionSkip: 5px;
mediaInBubbleSkip: 5px;
mediaThumbSize: 48px;

View File

@@ -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";
@@ -864,8 +867,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"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 +903,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";
@@ -990,7 +993,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";
@@ -999,7 +1002,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}?";
@@ -1092,18 +1095,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 :(";
@@ -1123,7 +1129,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";
@@ -1132,7 +1138,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";
@@ -1146,8 +1152,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)";
@@ -1234,7 +1240,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}";
@@ -1275,7 +1281,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}";
@@ -1296,7 +1302,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.";
@@ -1400,9 +1406,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}";

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="1.2.2.0" />
Version="1.2.6.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,2,2,0
PRODUCTVERSION 1,2,2,0
FILEVERSION 1,2,6,0
PRODUCTVERSION 1,2,6,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.2.0"
VALUE "FileVersion", "1.2.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.2.2.0"
VALUE "ProductVersion", "1.2.6.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,2,2,0
PRODUCTVERSION 1,2,2,0
FILEVERSION 1,2,6,0
PRODUCTVERSION 1,2,6,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.2.0"
VALUE "FileVersion", "1.2.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.2.2.0"
VALUE "ProductVersion", "1.2.6.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -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,72 +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.");
addLocalAlphaChangelog(1002002, "\xE2\x80\x94 Grouped photos and videos are displayed as albums.");
}
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) {
@@ -846,47 +887,65 @@ void ApiWrap::requestSelfParticipant(ChannelData *channel) {
_selfParticipantRequests.insert(channel, requestId);
}
void ApiWrap::kickParticipant(PeerData *peer, UserData *user, const MTPChannelBannedRights &currentRights) {
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 &currentRights) {
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(
@@ -1898,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: {
@@ -2566,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;
@@ -2587,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),
@@ -2618,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) {

View File

@@ -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 &currentRights);
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 &currentRights);
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;

View File

@@ -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"
@@ -128,7 +129,8 @@ namespace {
LastPhotosList lastPhotos;
using LastPhotosMap = QHash<PhotoData*, LastPhotosList::iterator>;
LastPhotosMap lastPhotosMap;
}
} // namespace
namespace App {
@@ -219,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
@@ -354,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;
@@ -649,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
@@ -1034,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;
@@ -1509,22 +1383,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;
@@ -1534,9 +1415,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;
@@ -1570,27 +1451,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) {
@@ -1608,7 +1501,7 @@ namespace {
convert->sticker()->loc = thumbLocation;
}
MediaKey newKey = convert->mediaKey();
const auto newKey = convert->mediaKey();
if (idChanged) {
if (convert->isVoiceMessage()) {
Local::copyAudio(oldKey, newKey);
@@ -1622,7 +1515,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) {
@@ -1699,10 +1592,23 @@ 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,
int32 duration,
const QString &author,
int32 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);
}
@@ -1726,7 +1632,7 @@ namespace {
if (App::main()) App::main()->webPageUpdated(convert);
}
}
auto i = webPagesData.constFind(webPage);
const auto i = webPagesData.constFind(webPage);
WebPageData *result;
if (i == webPagesData.cend()) {
if (convert) {
@@ -2117,15 +2023,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);
@@ -2133,7 +2030,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());
@@ -2147,12 +2044,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);
@@ -2164,10 +2061,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() {
@@ -2642,23 +2539,30 @@ 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);
@@ -2668,18 +2572,13 @@ namespace {
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);
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) {
@@ -2763,7 +2662,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;
}

View File

@@ -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);
@@ -270,8 +262,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);

View File

@@ -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) {
@@ -399,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();
});
@@ -408,7 +421,7 @@ AuthSession::AuthSession(UserId userId)
_shouldLockAt = 0;
notifications().updateAll();
});
_api->start();
Window::Theme::Background()->start();
}
bool AuthSession::Exists() {

View File

@@ -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;
}
@@ -273,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;
@@ -406,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;
};

View File

@@ -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);
}

View File

@@ -45,7 +45,7 @@ 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.data())) {
if (const auto url = dynamic_cast<UrlClickHandler*>(link.get())) {
url->UrlClickHandler::onClick(button);
return false;
}

View File

@@ -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);

View File

@@ -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));
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View 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;
}

View 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;
};

View File

@@ -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);

View File

@@ -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); });
};

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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,

View File

@@ -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)));

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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 &&copyCallback, 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) {

View File

@@ -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() {

View File

@@ -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()) {

View File

@@ -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);

View File

@@ -365,10 +365,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 +388,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();
@@ -490,7 +497,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();

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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();

View File

@@ -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;

View File

@@ -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();

View File

@@ -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,7 +1104,9 @@ 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;
}
@@ -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;
@@ -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));
}
}

View File

@@ -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;

View File

@@ -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());

View File

@@ -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 {

View File

@@ -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");

View File

@@ -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

View File

@@ -0,0 +1,190 @@
/*
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."
},
};
}
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

View 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

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -26,7 +26,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#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 +38,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 +78,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;
@@ -130,19 +142,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) {
base::TaskQueue::Main().Put([=] {
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 +173,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) {
base::TaskQueue::Main().Put([=] {
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 +200,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) {
base::TaskQueue::Main().Put([=] {
auto file = QString();
if (filedialogGetSaveFile(file, caption, filter, initialPath)) {
if (callback) {
callback(file);
callback(std::move(file));
}
} else if (failed) {
failed();
@@ -181,14 +218,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) {
base::TaskQueue::Main().Put([=] {
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();

View File

@@ -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

View File

@@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#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 {
@@ -63,11 +64,7 @@ int Launcher::exec() {
Logs::start(this); // must be started before Platform is started
Platform::start(); // must be started before QApplication is created
auto result = 0;
{
Application app(this, _argc, _argv);
result = app.exec();
}
auto result = executeApplication();
DEBUG_LOG(("Telegram finished, result: %1").arg(result));
@@ -240,4 +237,10 @@ void Launcher::processArguments() {
gStartUrl = parseResult.value("--", QStringList()).join(QString());
}
int Launcher::executeApplication() {
MainQueueProcessor processor;
Application app(this, _argc, _argv);
return app.exec();
}
} // namespace Core

View File

@@ -58,6 +58,7 @@ private:
virtual bool launchUpdater(UpdaterLaunch action) = 0;
int executeApplication();
int _argc;
char **_argv;

View 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

View 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

View File

@@ -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>

View File

@@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#define BETA_VERSION_MACRO (0ULL)
constexpr int AppVersion = 1002002;
constexpr str_const AppVersionStr = "1.2.2";
constexpr bool AppAlphaVersion = true;
constexpr int AppVersion = 1002006;
constexpr str_const AppVersionStr = "1.2.6";
constexpr bool AppAlphaVersion = false;
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;

View File

@@ -332,28 +332,15 @@ void DocumentOpenClickHandler::doOpen(
}
void DocumentOpenClickHandler::onClickImpl() const {
const auto item = context()
? App::histItemById(context())
: 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 = context()
? App::histItemById(context())
: App::hoveredLinkItem()
? App::hoveredLinkItem()
: App::contextItem()
? App::contextItem()
: nullptr;
doOpen(document(), item, ActionOnLoadPlayInline);
doOpen(document(), getActionItem(), ActionOnLoadPlayInline);
}
void DocumentSaveClickHandler::doSave(
@@ -382,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();
@@ -685,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.);
}
@@ -700,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) {

View File

@@ -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,24 +313,20 @@ private:
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit);
QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);
class DocumentClickHandler : public LeftButtonClickHandler {
class DocumentClickHandler : public FileClickHandler {
public:
DocumentClickHandler(
not_null<DocumentData*> document,
FullMsgId context = FullMsgId())
: _document(document)
, _context(context) {
: FileClickHandler(context)
, _document(document) {
}
not_null<DocumentData*> document() const {
return _document;
}
FullMsgId context() const {
return _context;
}
private:
not_null<DocumentData*> _document;
FullMsgId _context;
};

View File

@@ -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(
@@ -351,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;
}
@@ -491,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());
}
}
@@ -576,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) {
@@ -792,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);

View File

@@ -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

View File

@@ -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

View File

@@ -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() {

View File

@@ -44,6 +44,9 @@ public:
int32 loadOffset() const;
bool uploading() const;
void setWaitingForAlbum();
bool waitingForAlbum() const;
void forget();
ImagePtr makeReplyPreview();
@@ -57,27 +60,21 @@ 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)
, _context(context)
: FileClickHandler(context)
, _photo(photo)
, _peer(peer) {
}
not_null<PhotoData*> photo() const {
@@ -86,13 +83,9 @@ public:
PeerData *peer() const {
return _peer;
}
FullMsgId context() const {
return _context;
}
private:
not_null<PhotoData*> _photo;
FullMsgId _context;
PeerData *_peer = nullptr;
};

View File

@@ -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;
};

View File

@@ -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;
};
@@ -353,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);
});
}

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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);
});
}

View File

@@ -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"
@@ -96,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);
@@ -1797,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();
@@ -1811,7 +1812,7 @@ void DialogsInner::searchInPeer(PeerData *peer, UserData *from) {
_searchFromUserText.setText(
st::dialogsSearchFromStyle,
fromUserText,
_textDlgOptions);
Ui::DialogTextOptions());
_cancelSearchFromUser->show();
} else {
_cancelSearchFromUser->hide();

View File

@@ -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,

View File

@@ -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()) {

View File

@@ -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();
}
}
}

View File

@@ -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"
@@ -69,19 +70,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 +97,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;

View File

@@ -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();

View File

@@ -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,7 @@ 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 {
@@ -61,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);
@@ -69,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)
@@ -363,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());
@@ -543,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;
}
@@ -679,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;
}
@@ -1114,28 +1093,26 @@ not_null<HistoryItem*> History::addNewService(MsgId msgId, QDateTime date, const
}
HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type) {
if (isChannel()) {
return asChannelHistory()->addNewChannelMessage(msg, type);
}
if (type == NewMessageExisting) {
return addToHistory(msg);
}
if (!loadedAtBottom() || peer->migrateTo()) {
const auto 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) {
Expects(type != NewMessageExisting);
const auto applyServiceAction = (type == NewMessageUnread);
const auto detachExistingItem = (type != NewMessageLast);
const auto item = createItem(msg, applyServiceAction, detachExistingItem);
@@ -1186,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;
}
@@ -1273,29 +1252,15 @@ not_null<HistoryItem*> History::addNewItem(not_null<HistoryItem*> adding, bool n
if (groupFrom != groupTill || groupFrom->groupId()) {
recountGrouping(groupFrom, groupTill);
}
setLastMessage(adding);
if (newMsg) {
newItemAdded(adding);
}
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) {
@@ -1378,6 +1343,11 @@ not_null<HistoryItem*> History::addNewItem(not_null<HistoryItem*> adding, bool n
}
}
setLastMessage(adding);
if (newMsg) {
newItemAdded(adding);
}
return adding;
}
@@ -1398,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);
@@ -1408,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);
}
@@ -1598,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()) {
@@ -1767,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);
}
}
}
@@ -2416,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 {

View File

@@ -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*>>;
@@ -173,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 {
@@ -234,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();
@@ -369,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);
@@ -609,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;

View File

@@ -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);

View File

@@ -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 };
}

View File

@@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_service_layout.h"
#include "history/history_admin_log_section.h"
#include "history/history_admin_log_filter.h"
#include "history/history_item_components.h"
#include "chat_helpers/message_field.h"
#include "mainwindow.h"
#include "mainwidget.h"
@@ -219,12 +220,12 @@ InnerWidget::InnerWidget(
, _emptyText(st::historyAdminLogEmptyWidth - st::historyAdminLogEmptyPadding.left() - st::historyAdminLogEmptyPadding.left()) {
setMouseTracking(true);
_scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); });
Auth().data().itemRepaintRequest()
| rpl::start_with_next([this](auto item) {
if (item->isLogEntry() && _history == item->history()) {
repaintItem(item);
}
}, lifetime());
Auth().data().itemRepaintRequest(
) | rpl::start_with_next([this](auto item) {
if (item->isLogEntry() && _history == item->history()) {
repaintItem(item);
}
}, lifetime());
subscribe(Auth().data().pendingHistoryResize(), [this] { handlePendingHistoryResize(); });
subscribe(Auth().data().queryItemVisibility(), [this](const AuthSessionData::ItemVisibilityQuery &query) {
if (_history != query.item->history() || !query.item->isLogEntry() || !isVisible()) {
@@ -424,17 +425,17 @@ void InnerWidget::updateEmptyText() {
QString InnerWidget::tooltipText() const {
if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
if (auto item = App::hoveredItem()) {
if (const auto item = App::hoveredItem()) {
auto dateText = item->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
return dateText;
}
} else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) {
if (auto item = App::hoveredItem()) {
if (auto forwarded = item->Get<HistoryMessageForwarded>()) {
return forwarded->_text.originalText(AllTextSelection, ExpandLinksNone);
if (const auto item = App::hoveredItem()) {
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
return forwarded->text.originalText(AllTextSelection, ExpandLinksNone);
}
}
} else if (auto lnk = ClickHandler::getActive()) {
} else if (const auto lnk = ClickHandler::getActive()) {
return lnk->tooltip();
}
return QString();
@@ -821,9 +822,9 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_contextMenuLink = ClickHandler::getActive();
auto item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem();
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(_contextMenuLink.data());
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data());
auto lnkPeer = dynamic_cast<PeerClickHandler*>(_contextMenuLink.data());
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(_contextMenuLink.get());
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.get());
auto lnkPeer = dynamic_cast<PeerClickHandler*>(_contextMenuLink.get());
auto lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideoFile() : false;
auto lnkIsVoice = lnkDocument ? lnkDocument->document()->isVoiceMessage() : false;
auto lnkIsAudio = lnkDocument ? lnkDocument->document()->isAudioFile() : false;
@@ -979,7 +980,7 @@ void InnerWidget::showStickerPackInfo() {
}
void InnerWidget::cancelContextDownload() {
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data())) {
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.get())) {
lnkDocument->document()->cancel();
} else if (auto item = App::contextItem()) {
if (auto media = item->getMedia()) {
@@ -992,7 +993,7 @@ void InnerWidget::cancelContextDownload() {
void InnerWidget::showContextInFolder() {
QString filepath;
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data())) {
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.get())) {
filepath = lnkDocument->document()->filepath(DocumentData::FilePathResolveChecked);
} else if (auto item = App::contextItem()) {
if (auto media = item->getMedia()) {
@@ -1240,9 +1241,9 @@ void InnerWidget::mouseActionCancel() {
void InnerWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button) {
mouseActionUpdate(screenPos);
ClickHandlerPtr activated = ClickHandler::unpressed();
auto activated = ClickHandler::unpressed();
if (_mouseAction == MouseAction::Dragging) {
activated.clear();
activated = nullptr;
}
if (App::pressedItem()) {
repaintItem(App::pressedItem());

View File

@@ -488,7 +488,7 @@ void GenerateItems(not_null<History*> history, LocalIdManager &idManager, const
fromLinkText,
lt_sticker_set,
textcmdLink(2, lang(lng_admin_log_changed_stickers_set)));
auto setLink = MakeShared<LambdaClickHandler>([set] {
auto setLink = std::make_shared<LambdaClickHandler>([set] {
Ui::show(Box<StickerSetBox>(set));
});
auto message = HistoryService::PreparedText { text };

View File

@@ -294,7 +294,7 @@ not_null<ChannelData*> Widget::channel() const {
QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams &params) {
if (params.withTopBarShadow) _fixedBarShadow->hide();
auto result = myGrab(this);
auto result = Ui::GrabWidget(this);
if (params.withTopBarShadow) _fixedBarShadow->show();
return result;
}
@@ -319,7 +319,7 @@ bool Widget::showInternal(
void Widget::setInternalState(const QRect &geometry, not_null<SectionMemento*> memento) {
setGeometry(geometry);
myEnsureResized(this);
Ui::SendPendingMoveResizeEvents(this);
restoreState(memento);
}

View File

@@ -143,7 +143,9 @@ void DragArea::hideStart() {
return;
}
if (_cache.isNull()) {
_cache = myGrab(this, innerRect().marginsAdded(st::boxRoundShadow.extend));
_cache = Ui::GrabWidget(
this,
innerRect().marginsAdded(st::boxRoundShadow.extend));
}
_hiding = true;
setIn(false);
@@ -162,7 +164,9 @@ void DragArea::showStart() {
}
_hiding = false;
if (_cache.isNull()) {
_cache = myGrab(this, innerRect().marginsAdded(st::boxRoundShadow.extend));
_cache = Ui::GrabWidget(
this,
innerRect().marginsAdded(st::boxRoundShadow.extend));
}
show();
_a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::boxDuration);

View File

@@ -26,6 +26,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_message.h"
#include "history/history_service_layout.h"
#include "history/history_media_types.h"
#include "history/history_item_components.h"
#include "ui/text_options.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h"
#include "window/window_peer_menu.h"
@@ -158,19 +160,18 @@ HistoryInner::HistoryInner(
subscribe(_controller->window()->dragFinished(), [this] {
mouseActionUpdate(QCursor::pos());
});
Auth().data().itemRemoved()
| rpl::start_with_next(
[this](auto item) { itemRemoved(item); },
lifetime());
Auth().data().itemRemoved(
) | rpl::start_with_next(
[this](auto item) { itemRemoved(item); },
lifetime());
rpl::merge(
Auth().data().historyUnloaded(),
Auth().data().historyCleared())
| rpl::filter([this](not_null<const History*> history) {
return (_history == history);
})
| rpl::start_with_next([this] {
mouseActionCancel();
}, lifetime());
Auth().data().historyCleared()
) | rpl::filter([this](not_null<const History*> history) {
return (_history == history);
}) | rpl::start_with_next([this] {
mouseActionCancel();
}, lifetime());
}
void HistoryInner::messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages) {
@@ -1031,7 +1032,7 @@ void HistoryInner::performDrag() {
}
auto pressedHandler = ClickHandler::getPressed();
if (dynamic_cast<VoiceSeekClickHandler*>(pressedHandler.data())) {
if (dynamic_cast<VoiceSeekClickHandler*>(pressedHandler.get())) {
return;
}
@@ -1127,14 +1128,14 @@ void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton bu
auto activated = ClickHandler::unpressed();
if (_mouseAction == MouseAction::Dragging) {
activated.clear();
activated = nullptr;
} else if (_mouseActionItem) {
// if we are in selecting items mode perhaps we want to
// toggle selection instead of activating the pressed link
if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && !_selected.empty() && _selected.cbegin()->second == FullSelection && button != Qt::RightButton) {
if (auto media = _mouseActionItem->getMedia()) {
if (media->toggleSelectionByHandlerClick(activated)) {
activated.clear();
activated = nullptr;
}
}
}
@@ -1291,8 +1292,8 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_menu = new Ui::PopupMenu(nullptr);
_contextMenuLink = ClickHandler::getActive();
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(_contextMenuLink.data());
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data());
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(_contextMenuLink.get());
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.get());
auto lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideoFile() : false;
auto lnkIsVoice = lnkDocument ? lnkDocument->document()->isVoiceMessage() : false;
auto lnkIsAudio = lnkDocument ? lnkDocument->document()->isAudioFile() : false;
@@ -1387,14 +1388,16 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
auto item = App::hoveredItem()
? App::hoveredItem()
: App::hoveredLinkItem();
const auto group = item->getFullGroup();
if (group) {
if (const auto group = item ? item->getFullGroup() : nullptr) {
item = group->others.empty()
? group->leader
: group->others.front().get();
}
bool canDelete = item && item->canDelete() && (item->id > 0 || !item->serviceMsg());
bool canForward = item && item->canForward();
const auto canDelete = item
&& item->canDelete()
&& (item->id > 0 || !item->serviceMsg());
const auto canForward = item
&& item->canForward();
auto msg = dynamic_cast<HistoryMessage*>(item);
if (isUponSelected > 0) {
@@ -1592,7 +1595,7 @@ void HistoryInner::toggleFavedSticker(DocumentData *document) {
}
void HistoryInner::cancelContextDownload() {
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data())) {
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.get())) {
lnkDocument->document()->cancel();
} else if (auto item = App::contextItem()) {
if (auto media = item->getMedia()) {
@@ -1605,7 +1608,7 @@ void HistoryInner::cancelContextDownload() {
void HistoryInner::showContextInFolder() {
QString filepath;
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data())) {
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.get())) {
filepath = lnkDocument->document()->filepath(DocumentData::FilePathResolveChecked);
} else if (auto item = App::contextItem()) {
if (auto media = item->getMedia()) {
@@ -1834,7 +1837,10 @@ void HistoryInner::updateBotInfo(bool recount) {
int newh = 0;
if (_botAbout && !_botAbout->info->description.isEmpty()) {
if (_botAbout->info->text.isEmpty()) {
_botAbout->info->text.setText(st::messageTextStyle, _botAbout->info->description, _historyBotNoMonoOptions);
_botAbout->info->text.setText(
st::messageTextStyle,
_botAbout->info->description,
Ui::ItemTextBotNoMonoOptions());
if (recount) {
int32 tw = _scroll->width() - st::msgMargin.left() - st::msgMargin.right();
if (tw > st::msgMaxWidth) tw = st::msgMaxWidth;
@@ -2284,9 +2290,9 @@ void HistoryInner::onUpdateSelected() {
if (point.x() >= dateLeft && point.x() < dateLeft + dateWidth) {
if (!_scrollDateLink) {
_scrollDateLink = MakeShared<DateClickHandler>(item->history()->peer, item->date.date());
_scrollDateLink = std::make_shared<DateClickHandler>(item->history()->peer, item->date.date());
} else {
static_cast<DateClickHandler*>(_scrollDateLink.data())->setDate(item->date.date());
static_cast<DateClickHandler*>(_scrollDateLink.get())->setDate(item->date.date());
}
dragState = HistoryTextState(
nullptr,
@@ -2772,21 +2778,22 @@ void HistoryInner::applyDragSelection(
QString HistoryInner::tooltipText() const {
if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
if (App::hoveredItem()) {
auto dateText = App::hoveredItem()->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
auto editedDate = App::hoveredItem()->displayedEditDate();
if (const auto item = App::hoveredItem()) {
auto dateText = item->date.toString(
QLocale::system().dateTimeFormat(QLocale::LongFormat));
auto editedDate = item->displayedEditDate();
if (!editedDate.isNull()) {
dateText += '\n' + lng_edited_date(lt_date, editedDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
}
if (auto forwarded = App::hoveredItem()->Get<HistoryMessageForwarded>()) {
dateText += '\n' + lng_forwarded_date(lt_date, forwarded->_originalDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
dateText += '\n' + lng_forwarded_date(lt_date, forwarded->originalDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
}
return dateText;
}
} else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) {
if (App::hoveredItem()) {
if (auto forwarded = App::hoveredItem()->Get<HistoryMessageForwarded>()) {
return forwarded->_text.originalText(AllTextSelection, ExpandLinksNone);
if (const auto item = App::hoveredItem()) {
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
return forwarded->text.originalText(AllTextSelection, ExpandLinksNone);
}
}
} else if (auto lnk = ClickHandler::getActive()) {

View File

@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "history/history_item_components.h"
#include "history/history_service_layout.h"
#include "history/history_media_types.h"
#include "history/history_media_grouped.h"
@@ -30,10 +31,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_dialogs.h"
#include "styles/style_history.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text_options.h"
#include "storage/file_upload.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "auth_session.h"
#include "apiwrap.h"
#include "media/media_audio.h"
#include "messenger.h"
#include "mainwindow.h"
@@ -69,522 +72,6 @@ HistoryTextState::HistoryTextState(
, link(link) {
}
ReplyMarkupClickHandler::ReplyMarkupClickHandler(const HistoryItem *item, int row, int col)
: _itemId(item->fullId())
, _row(row)
, _col(col) {
}
// Copy to clipboard support.
void ReplyMarkupClickHandler::copyToClipboard() const {
if (auto button = getButton()) {
if (button->type == HistoryMessageReplyMarkup::Button::Type::Url) {
auto url = QString::fromUtf8(button->data);
if (!url.isEmpty()) {
QApplication::clipboard()->setText(url);
}
}
}
}
QString ReplyMarkupClickHandler::copyToClipboardContextItemText() const {
if (auto button = getButton()) {
if (button->type == HistoryMessageReplyMarkup::Button::Type::Url) {
return lang(lng_context_copy_link);
}
}
return QString();
}
// Finds the corresponding button in the items markup struct.
// If the button is not found it returns nullptr.
// Note: it is possible that we will point to the different button
// than the one was used when constructing the handler, but not a big deal.
const HistoryMessageReplyMarkup::Button *ReplyMarkupClickHandler::getButton() const {
if (auto item = App::histItemById(_itemId)) {
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
if (_row < markup->rows.size()) {
auto &row = markup->rows.at(_row);
if (_col < row.size()) {
return &row.at(_col);
}
}
}
}
return nullptr;
}
void ReplyMarkupClickHandler::onClickImpl() const {
if (auto item = App::histItemById(_itemId)) {
App::activateBotCommand(item, _row, _col);
}
}
// Returns the full text of the corresponding button.
QString ReplyMarkupClickHandler::buttonText() const {
if (auto button = getButton()) {
return button->text;
}
return QString();
}
ReplyKeyboard::ReplyKeyboard(const HistoryItem *item, StylePtr &&s)
: _item(item)
, _a_selected(animation(this, &ReplyKeyboard::step_selected))
, _st(std::move(s)) {
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
_rows.reserve(markup->rows.size());
for (int i = 0, l = markup->rows.size(); i != l; ++i) {
auto &row = markup->rows.at(i);
int s = row.size();
ButtonRow newRow(s, Button());
for (int j = 0; j != s; ++j) {
auto &button = newRow[j];
auto str = row.at(j).text;
button.type = row.at(j).type;
button.link = MakeShared<ReplyMarkupClickHandler>(item, i, j);
button.text.setText(_st->textStyle(), TextUtilities::SingleLine(str), _textPlainOptions);
button.characters = str.isEmpty() ? 1 : str.size();
}
_rows.push_back(newRow);
}
}
}
void ReplyKeyboard::updateMessageId() {
auto msgId = _item->fullId();
for_const (auto &row, _rows) {
for_const (auto &button, row) {
button.link->setMessageId(msgId);
}
}
}
void ReplyKeyboard::resize(int width, int height) {
_width = width;
auto markup = _item->Get<HistoryMessageReplyMarkup>();
float64 y = 0, buttonHeight = _rows.isEmpty() ? _st->buttonHeight() : (float64(height + _st->buttonSkip()) / _rows.size());
for (auto &row : _rows) {
int s = row.size();
int widthForButtons = _width - ((s - 1) * _st->buttonSkip());
int widthForText = widthForButtons;
int widthOfText = 0;
int maxMinButtonWidth = 0;
for_const (auto &button, row) {
widthOfText += qMax(button.text.maxWidth(), 1);
int minButtonWidth = _st->minButtonWidth(button.type);
widthForText -= minButtonWidth;
accumulate_max(maxMinButtonWidth, minButtonWidth);
}
bool exact = (widthForText == widthOfText);
bool enough = (widthForButtons - s * maxMinButtonWidth) >= widthOfText;
float64 x = 0;
for (Button &button : row) {
int buttonw = qMax(button.text.maxWidth(), 1);
float64 textw = buttonw, minw = _st->minButtonWidth(button.type);
float64 w = textw;
if (exact) {
w += minw;
} else if (enough) {
w = (widthForButtons / float64(s));
textw = w - minw;
} else {
textw = (widthForText / float64(s));
w = minw + textw;
accumulate_max(w, 2 * float64(_st->buttonPadding()));
}
int rectx = static_cast<int>(std::floor(x));
int rectw = static_cast<int>(std::floor(x + w)) - rectx;
button.rect = QRect(rectx, qRound(y), rectw, qRound(buttonHeight - _st->buttonSkip()));
if (rtl()) button.rect.setX(_width - button.rect.x() - button.rect.width());
x += w + _st->buttonSkip();
button.link->setFullDisplayed(textw >= buttonw);
}
y += buttonHeight;
}
}
bool ReplyKeyboard::isEnoughSpace(int width, const style::BotKeyboardButton &st) const {
for_const (auto &row, _rows) {
int s = row.size();
int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding);
for_const (auto &button, row) {
widthLeft -= qMax(button.text.maxWidth(), 1);
if (widthLeft < 0) {
if (row.size() > 3) {
return false;
} else {
break;
}
}
}
}
return true;
}
void ReplyKeyboard::setStyle(StylePtr &&st) {
_st = std::move(st);
}
int ReplyKeyboard::naturalWidth() const {
auto result = 0;
for_const (auto &row, _rows) {
auto maxMinButtonWidth = 0;
for_const (auto &button, row) {
accumulate_max(maxMinButtonWidth, _st->minButtonWidth(button.type));
}
auto rowMaxButtonWidth = 0;
for_const (auto &button, row) {
accumulate_max(rowMaxButtonWidth, qMax(button.text.maxWidth(), 1) + maxMinButtonWidth);
}
auto rowSize = row.size();
accumulate_max(result, rowSize * rowMaxButtonWidth + (rowSize - 1) * _st->buttonSkip());
}
return result;
}
int ReplyKeyboard::naturalHeight() const {
return (_rows.size() - 1) * _st->buttonSkip() + _rows.size() * _st->buttonHeight();
}
void ReplyKeyboard::paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const {
Assert(_st != nullptr);
Assert(_width > 0);
_st->startPaint(p);
for_const (auto &row, _rows) {
for_const (auto &button, row) {
QRect rect(button.rect);
if (rect.y() >= clip.y() + clip.height()) return;
if (rect.y() + rect.height() < clip.y()) continue;
// just ignore the buttons that didn't layout well
if (rect.x() + rect.width() > _width) break;
_st->paintButton(p, outerWidth, button, ms);
}
}
}
ClickHandlerPtr ReplyKeyboard::getState(QPoint point) const {
Assert(_width > 0);
for_const (auto &row, _rows) {
for_const (auto &button, row) {
QRect rect(button.rect);
// just ignore the buttons that didn't layout well
if (rect.x() + rect.width() > _width) break;
if (rect.contains(point)) {
_savedCoords = point;
return button.link;
}
}
}
return ClickHandlerPtr();
}
void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (!p) return;
_savedActive = active ? p : ClickHandlerPtr();
auto coords = findButtonCoordsByClickHandler(p);
if (coords.i >= 0 && _savedPressed != p) {
startAnimation(coords.i, coords.j, active ? 1 : -1);
}
}
ReplyKeyboard::ButtonCoords ReplyKeyboard::findButtonCoordsByClickHandler(const ClickHandlerPtr &p) {
for (int i = 0, rows = _rows.size(); i != rows; ++i) {
auto &row = _rows[i];
for (int j = 0, cols = row.size(); j != cols; ++j) {
if (row[j].link == p) {
return { i, j };
}
}
}
return { -1, -1 };
}
void ReplyKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
if (!p) return;
_savedPressed = pressed ? p : ClickHandlerPtr();
auto coords = findButtonCoordsByClickHandler(p);
if (coords.i >= 0) {
auto &button = _rows[coords.i][coords.j];
if (pressed) {
if (!button.ripple) {
auto mask = Ui::RippleAnimation::roundRectMask(button.rect.size(), _st->buttonRadius());
button.ripple = MakeShared<Ui::RippleAnimation>(_st->_st->ripple, std::move(mask), [this] { _st->repaint(_item); });
}
button.ripple->add(_savedCoords - button.rect.topLeft());
} else {
if (button.ripple) {
button.ripple->lastStop();
}
if (_savedActive != p) {
startAnimation(coords.i, coords.j, -1);
}
}
}
}
void ReplyKeyboard::startAnimation(int i, int j, int direction) {
auto notStarted = _animations.isEmpty();
int indexForAnimation = (i * MatrixRowShift + j + 1) * direction;
_animations.remove(-indexForAnimation);
if (!_animations.contains(indexForAnimation)) {
_animations.insert(indexForAnimation, getms());
}
if (notStarted && !_a_selected.animating()) {
_a_selected.start();
}
}
void ReplyKeyboard::step_selected(TimeMs ms, bool timer) {
for (Animations::iterator i = _animations.begin(); i != _animations.end();) {
int index = qAbs(i.key()) - 1, row = (index / MatrixRowShift), col = index % MatrixRowShift;
float64 dt = float64(ms - i.value()) / st::botKbDuration;
if (dt >= 1) {
_rows[row][col].howMuchOver = (i.key() > 0) ? 1 : 0;
i = _animations.erase(i);
} else {
_rows[row][col].howMuchOver = (i.key() > 0) ? dt : (1 - dt);
++i;
}
}
if (timer) _st->repaint(_item);
if (_animations.isEmpty()) {
_a_selected.stop();
}
}
void ReplyKeyboard::clearSelection() {
for (auto i = _animations.cbegin(), e = _animations.cend(); i != e; ++i) {
int index = qAbs(i.key()) - 1, row = (index / MatrixRowShift), col = index % MatrixRowShift;
_rows[row][col].howMuchOver = 0;
}
_animations.clear();
_a_selected.stop();
}
int ReplyKeyboard::Style::buttonSkip() const {
return _st->margin;
}
int ReplyKeyboard::Style::buttonPadding() const {
return _st->padding;
}
int ReplyKeyboard::Style::buttonHeight() const {
return _st->height;
}
void ReplyKeyboard::Style::paintButton(Painter &p, int outerWidth, const ReplyKeyboard::Button &button, TimeMs ms) const {
const QRect &rect = button.rect;
paintButtonBg(p, rect, button.howMuchOver);
if (button.ripple) {
button.ripple->paint(p, rect.x(), rect.y(), outerWidth, ms);
if (button.ripple->empty()) {
button.ripple.reset();
}
}
paintButtonIcon(p, rect, outerWidth, button.type);
if (button.type == HistoryMessageReplyMarkup::Button::Type::Callback
|| button.type == HistoryMessageReplyMarkup::Button::Type::Game) {
if (auto data = button.link->getButton()) {
if (data->requestId) {
paintButtonLoading(p, rect);
}
}
}
int tx = rect.x(), tw = rect.width();
if (tw >= st::botKbStyle.font->elidew + _st->padding * 2) {
tx += _st->padding;
tw -= _st->padding * 2;
} else if (tw > st::botKbStyle.font->elidew) {
tx += (tw - st::botKbStyle.font->elidew) / 2;
tw = st::botKbStyle.font->elidew;
}
button.text.drawElided(p, tx, rect.y() + _st->textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top);
}
void HistoryMessageReplyMarkup::createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v) {
if (v.isEmpty()) {
rows.clear();
return;
}
rows.reserve(v.size());
for_const (auto &row, v) {
switch (row.type()) {
case mtpc_keyboardButtonRow: {
auto &r = row.c_keyboardButtonRow();
auto &b = r.vbuttons.v;
if (!b.isEmpty()) {
ButtonRow buttonRow;
buttonRow.reserve(b.size());
for_const (auto &button, b) {
switch (button.type()) {
case mtpc_keyboardButton: {
buttonRow.push_back({ Button::Type::Default, qs(button.c_keyboardButton().vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonCallback: {
auto &buttonData = button.c_keyboardButtonCallback();
buttonRow.push_back({ Button::Type::Callback, qs(buttonData.vtext), qba(buttonData.vdata), 0 });
} break;
case mtpc_keyboardButtonRequestGeoLocation: {
buttonRow.push_back({ Button::Type::RequestLocation, qs(button.c_keyboardButtonRequestGeoLocation().vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonRequestPhone: {
buttonRow.push_back({ Button::Type::RequestPhone, qs(button.c_keyboardButtonRequestPhone().vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonUrl: {
auto &buttonData = button.c_keyboardButtonUrl();
buttonRow.push_back({ Button::Type::Url, qs(buttonData.vtext), qba(buttonData.vurl), 0 });
} break;
case mtpc_keyboardButtonSwitchInline: {
auto &buttonData = button.c_keyboardButtonSwitchInline();
auto buttonType = buttonData.is_same_peer() ? Button::Type::SwitchInlineSame : Button::Type::SwitchInline;
buttonRow.push_back({ buttonType, qs(buttonData.vtext), qba(buttonData.vquery), 0 });
if (buttonType == Button::Type::SwitchInline) {
// Optimization flag.
// Fast check on all new messages if there is a switch button to auto-click it.
flags |= MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button;
}
} break;
case mtpc_keyboardButtonGame: {
auto &buttonData = button.c_keyboardButtonGame();
buttonRow.push_back({ Button::Type::Game, qs(buttonData.vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonBuy: {
auto &buttonData = button.c_keyboardButtonBuy();
buttonRow.push_back({ Button::Type::Buy, qs(buttonData.vtext), QByteArray(), 0 });
}
}
}
if (!buttonRow.isEmpty()) rows.push_back(buttonRow);
}
} break;
}
}
}
void HistoryMessageReplyMarkup::create(const MTPReplyMarkup &markup) {
flags = 0;
rows.clear();
inlineKeyboard = nullptr;
switch (markup.type()) {
case mtpc_replyKeyboardMarkup: {
auto &d = markup.c_replyKeyboardMarkup();
flags = d.vflags.v;
createFromButtonRows(d.vrows.v);
} break;
case mtpc_replyInlineMarkup: {
auto &d = markup.c_replyInlineMarkup();
flags = MTPDreplyKeyboardMarkup::Flags(0) | MTPDreplyKeyboardMarkup_ClientFlag::f_inline;
createFromButtonRows(d.vrows.v);
} break;
case mtpc_replyKeyboardHide: {
auto &d = markup.c_replyKeyboardHide();
flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_zero;
} break;
case mtpc_replyKeyboardForceReply: {
auto &d = markup.c_replyKeyboardForceReply();
flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply;
} break;
}
}
void HistoryMessageReplyMarkup::create(const HistoryMessageReplyMarkup &markup) {
flags = markup.flags;
inlineKeyboard = nullptr;
rows.clear();
for_const (auto &row, markup.rows) {
ButtonRow buttonRow;
buttonRow.reserve(row.size());
for_const (auto &button, row) {
buttonRow.push_back({ button.type, button.text, button.data, 0 });
}
if (!buttonRow.isEmpty()) rows.push_back(buttonRow);
}
}
void HistoryMessageUnreadBar::init(int count) {
if (_freezed) return;
_text = lng_unread_bar(lt_count, count);
_width = st::semiboldFont->width(_text);
}
int HistoryMessageUnreadBar::height() {
return st::historyUnreadBarHeight + st::historyUnreadBarMargin;
}
int HistoryMessageUnreadBar::marginTop() {
return st::lineWidth + st::historyUnreadBarMargin;
}
void HistoryMessageUnreadBar::paint(Painter &p, int y, int w) const {
p.fillRect(0, y + marginTop(), w, height() - marginTop() - st::lineWidth, st::historyUnreadBarBg);
p.fillRect(0, y + height() - st::lineWidth, w, st::lineWidth, st::historyUnreadBarBorder);
p.setFont(st::historyUnreadBarFont);
p.setPen(st::historyUnreadBarFg);
int left = st::msgServiceMargin.left();
int maxwidth = w;
if (Adaptive::ChatWide()) {
maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
}
w = maxwidth;
p.drawText((w - _width) / 2, y + marginTop() + (st::historyUnreadBarHeight - 2 * st::lineWidth - st::historyUnreadBarFont->height) / 2 + st::historyUnreadBarFont->ascent, _text);
}
void HistoryMessageDate::init(const QDateTime &date) {
_text = langDayOfMonthFull(date.date());
_width = st::msgServiceFont->width(_text);
}
int HistoryMessageDate::height() const {
return st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom() + st::msgServiceMargin.bottom();
}
void HistoryMessageDate::paint(Painter &p, int y, int w) const {
HistoryLayout::ServiceMessagePainter::paintDate(p, _text, _width, y, w);
}
HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal() = default;
HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other) : _page(std::move(other._page)) {
}
HistoryMessageLogEntryOriginal &HistoryMessageLogEntryOriginal::operator=(HistoryMessageLogEntryOriginal &&other) {
_page = std::move(other._page);
return *this;
}
HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default;
HistoryMediaPtr::HistoryMediaPtr() = default;
HistoryMediaPtr::HistoryMediaPtr(std::unique_ptr<HistoryMedia> pointer)
@@ -670,6 +157,22 @@ void HistoryItem::finishEdition(int oldKeyboardTop) {
App::historyUpdateDependent(this);
}
HistoryMessageReplyMarkup *HistoryItem::inlineReplyMarkup() {
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
return markup;
}
}
return nullptr;
}
ReplyKeyboard *HistoryItem::inlineReplyKeyboard() {
if (const auto markup = inlineReplyMarkup()) {
return markup->inlineKeyboard.get();
}
return nullptr;
}
void HistoryItem::invalidateChatsListEntry() {
if (App::main()) {
App::main()->dlgUpdated(history()->peer, id);
@@ -726,6 +229,31 @@ void HistoryItem::markMediaRead() {
}
}
bool HistoryItem::definesReplyKeyboard() const {
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
return false;
}
return true;
}
// optimization: don't create markup component for the case
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
return (_flags & MTPDmessage::Flag::f_reply_markup);
}
MTPDreplyKeyboardMarkup::Flags HistoryItem::replyKeyboardFlags() const {
Expects(definesReplyKeyboard());
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
return markup->flags;
}
// optimization: don't create markup component for the case
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
return MTPDreplyKeyboardMarkup_ClientFlag::f_zero | 0;
}
void HistoryItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
if (markup->inlineKeyboard) {
@@ -754,6 +282,13 @@ void HistoryItem::addLogEntryOriginal(WebPageId localId, const QString &label, c
original->_page = std::make_unique<HistoryWebPage>(this, webpage);
}
UserData *HistoryItem::viaBot() const {
if (const auto via = Get<HistoryMessageVia>()) {
return via->bot;
}
return nullptr;
}
void HistoryItem::destroy() {
if (isLogEntry()) {
Assert(detached());
@@ -761,18 +296,20 @@ void HistoryItem::destroy() {
// All this must be done for all items manually in History::clear(false)!
eraseFromUnreadMentions();
if (IsServerMsgId(id)) {
if (auto types = sharedMediaTypes()) {
if (const auto types = sharedMediaTypes()) {
Auth().storage().remove(Storage::SharedMediaRemoveOne(
history()->peer->id,
types,
id));
}
} else {
Auth().api().cancelLocalItem(this);
}
auto wasAtBottom = history()->loadedAtBottom();
_history->removeNotification(this);
detach();
if (auto channel = history()->peer->asChannel()) {
if (const auto channel = history()->peer->asChannel()) {
if (channel->pinnedMessageId() == id) {
channel->clearPinnedMessage();
}
@@ -817,6 +354,18 @@ Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const {
return {};
}
void HistoryItem::indexAsNewItem() {
if (IsServerMsgId(id)) {
addToUnreadMentions(UnreadMentionType::New);
if (const auto types = sharedMediaTypes()) {
Auth().storage().add(Storage::SharedMediaAddNew(
history()->peer->id,
types,
id));
}
}
}
void HistoryItem::previousItemChanged() {
Expects(!isLogEntry());
recountDisplayDate();
@@ -891,7 +440,14 @@ void HistoryItem::setId(MsgId newId) {
}
if (_media) {
_media->updateMessageId();
_media->refreshParentId(this);
if (const auto group = Get<HistoryMessageGroup>()) {
if (group->leader != this) {
if (const auto media = group->leader->getMedia()) {
media->refreshParentId(group->leader);
}
}
}
}
}
@@ -1071,6 +627,53 @@ QString HistoryItem::directLink() const {
return QString();
}
MsgId HistoryItem::replyToId() const {
if (auto reply = Get<HistoryMessageReply>()) {
return reply->replyToId();
}
return 0;
}
QDateTime HistoryItem::dateOriginal() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->originalDate;
}
return date;
}
PeerData *HistoryItem::senderOriginal() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->originalSender;
}
const auto peer = history()->peer;
return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
}
PeerData *HistoryItem::fromOriginal() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
if (const auto user = forwarded->originalSender->asUser()) {
return user;
}
}
return from();
}
QString HistoryItem::authorOriginal() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->originalAuthor;
} else if (const auto msgsigned = Get<HistoryMessageSigned>()) {
return msgsigned->author;
}
return QString();
}
MsgId HistoryItem::idOriginal() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->originalId;
}
return id;
}
bool HistoryItem::hasOutLayout() const {
if (history()->peer->isSelf()) {
return !Has<HistoryMessageForwarded>();
@@ -1154,11 +757,19 @@ void HistoryItem::setUnreadBarCount(int count) {
void HistoryItem::setUnreadBarFreezed() {
Expects(!isLogEntry());
if (auto bar = Get<HistoryMessageUnreadBar>()) {
if (const auto bar = Get<HistoryMessageUnreadBar>()) {
bar->_freezed = true;
}
}
MessageGroupId HistoryItem::groupId() const {
if (const auto group = Get<HistoryMessageGroup>()) {
return group->groupId;
}
return MessageGroupId::None;
}
bool HistoryItem::groupIdValidityChanged() {
if (Has<HistoryMessageGroup>()) {
if (_media && _media->canBeGrouped()) {
@@ -1200,6 +811,7 @@ void HistoryItem::makeGroupLeader(
const auto leaderChanged = (group->leader != this);
if (leaderChanged) {
group->leader = this;
_flags &= ~MTPDmessage_ClientFlag::f_hidden_by_group;
setPendingInitDimensions();
}
@@ -1232,6 +844,13 @@ void HistoryItem::resetGroupMedia(
setPendingInitDimensions();
}
int HistoryItem::displayedDateHeight() const {
if (auto date = Get<HistoryMessageDate>()) {
return date->height();
}
return 0;
}
int HistoryItem::marginTop() const {
auto result = 0;
if (!isHiddenByGroup()) {
@@ -1248,6 +867,16 @@ int HistoryItem::marginTop() const {
return result;
}
bool HistoryItem::displayDate() const {
return Has<HistoryMessageDate>();
}
bool HistoryItem::isEmpty() const {
return _text.isEmpty()
&& !_media
&& !Has<HistoryMessageLogEntryOriginal>();
}
int HistoryItem::marginBottom() const {
return isHiddenByGroup() ? 0 : st::msgMargin.bottom();
}
@@ -1404,7 +1033,7 @@ void HistoryItem::drawInDialog(
Text &cache) const {
if (cacheFor != this) {
cacheFor = this;
cache.setText(st::dialogsTextStyle, inDialogsText(way), _textDlgOptions);
cache.setText(st::dialogsTextStyle, inDialogsText(way), Ui::DialogTextOptions());
}
if (r.width()) {
p.setTextPalette(active ? st::dialogsTextPaletteActive : (selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette));
@@ -1423,7 +1052,7 @@ HistoryItem::~HistoryItem() {
}
ClickHandlerPtr goToMessageClickHandler(PeerData *peer, MsgId msgId) {
return MakeShared<LambdaClickHandler>([peer, msgId] {
return std::make_shared<LambdaClickHandler>([peer, msgId] {
if (App::main()) {
auto current = App::mousedItem();
if (current && current->history()->peer == peer) {

View File

@@ -24,6 +24,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "base/flags.h"
#include "base/value_ordering.h"
struct MessageGroupId;
struct HistoryMessageGroup;
struct HistoryMessageReplyMarkup;
class ReplyKeyboard;
class HistoryMessage;
class HistoryMedia;
namespace base {
template <typename Enum>
class enum_mask;
@@ -68,8 +75,6 @@ protected:
};
class HistoryMessage;
enum HistoryCursorState {
HistoryDefaultCursorState,
HistoryInTextCursorState,
@@ -123,371 +128,8 @@ enum InfoDisplayType {
InfoDisplayOverBackground,
};
struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia> {
void create(int32 userId);
void resize(int32 availw) const;
UserData *_bot = nullptr;
mutable QString _text;
mutable int _width = 0;
mutable int _maxWidth = 0;
ClickHandlerPtr _lnk;
};
struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews> {
QString _viewsText;
int _views = 0;
int _viewsWidth = 0;
};
struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned> {
void create(const QString &author, const QString &date);
int maxWidth() const;
QString _author;
Text _signature;
};
struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited> {
void refresh(const QString &date, bool displayed);
int maxWidth() const;
QDateTime date;
Text text;
};
struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded> {
void create(const HistoryMessageVia *via) const;
QDateTime _originalDate;
PeerData *_originalSender = nullptr;
QString _originalAuthor;
MsgId _originalId = 0;
mutable Text _text = { 1 };
PeerData *_savedFromPeer = nullptr;
MsgId _savedFromMsgId = 0;
};
struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply> {
HistoryMessageReply() = default;
HistoryMessageReply(const HistoryMessageReply &other) = delete;
HistoryMessageReply(HistoryMessageReply &&other) = delete;
HistoryMessageReply &operator=(const HistoryMessageReply &other) = delete;
HistoryMessageReply &operator=(HistoryMessageReply &&other) {
replyToMsgId = other.replyToMsgId;
std::swap(replyToMsg, other.replyToMsg);
replyToLnk = std::move(other.replyToLnk);
replyToName = std::move(other.replyToName);
replyToText = std::move(other.replyToText);
replyToVersion = other.replyToVersion;
_maxReplyWidth = other._maxReplyWidth;
_replyToVia = std::move(other._replyToVia);
return *this;
}
~HistoryMessageReply() {
// clearData() should be called by holder
Expects(replyToMsg == nullptr);
Expects(_replyToVia == nullptr);
}
bool updateData(HistoryMessage *holder, bool force = false);
void clearData(HistoryMessage *holder); // must be called before destructor
bool isNameUpdated() const;
void updateName() const;
void resize(int width) const;
void itemRemoved(HistoryMessage *holder, HistoryItem *removed);
enum class PaintFlag {
InBubble = (1 << 0),
Selected = (1 << 1),
};
using PaintFlags = base::flags<PaintFlag>;
friend inline constexpr auto is_flag_type(PaintFlag) { return true; };
void paint(Painter &p, const HistoryItem *holder, int x, int y, int w, PaintFlags flags) const;
MsgId replyToId() const {
return replyToMsgId;
}
int replyToWidth() const {
return _maxReplyWidth;
}
ClickHandlerPtr replyToLink() const {
return replyToLnk;
}
MsgId replyToMsgId = 0;
HistoryItem *replyToMsg = nullptr;
ClickHandlerPtr replyToLnk;
mutable Text replyToName, replyToText;
mutable int replyToVersion = 0;
mutable int _maxReplyWidth = 0;
std::unique_ptr<HistoryMessageVia> _replyToVia;
int toWidth = 0;
};
class ReplyKeyboard;
struct HistoryMessageReplyMarkup : public RuntimeComponent<HistoryMessageReplyMarkup> {
HistoryMessageReplyMarkup() = default;
HistoryMessageReplyMarkup(MTPDreplyKeyboardMarkup::Flags f) : flags(f) {
}
void create(const MTPReplyMarkup &markup);
void create(const HistoryMessageReplyMarkup &markup);
struct Button {
enum class Type {
Default,
Url,
Callback,
RequestPhone,
RequestLocation,
SwitchInline,
SwitchInlineSame,
Game,
Buy,
};
Type type;
QString text;
QByteArray data;
mutable mtpRequestId requestId;
};
using ButtonRow = QVector<Button>;
using ButtonRows = QVector<ButtonRow>;
ButtonRows rows;
MTPDreplyKeyboardMarkup::Flags flags = 0;
std::unique_ptr<ReplyKeyboard> inlineKeyboard;
// If >= 0 it holds the y coord of the inlineKeyboard before the last edition.
int oldTop = -1;
private:
void createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v);
};
class ReplyMarkupClickHandler : public LeftButtonClickHandler {
public:
ReplyMarkupClickHandler(const HistoryItem *item, int row, int col);
QString tooltip() const override {
return _fullDisplayed ? QString() : buttonText();
}
void setFullDisplayed(bool full) {
_fullDisplayed = full;
}
// Copy to clipboard support.
void copyToClipboard() const override;
QString copyToClipboardContextItemText() const override;
// Finds the corresponding button in the items markup struct.
// If the button is not found it returns nullptr.
// Note: it is possible that we will point to the different button
// than the one was used when constructing the handler, but not a big deal.
const HistoryMessageReplyMarkup::Button *getButton() const;
// We hold only FullMsgId, not HistoryItem*, because all click handlers
// are activated async and the item may be already destroyed.
void setMessageId(const FullMsgId &msgId) {
_itemId = msgId;
}
protected:
void onClickImpl() const override;
private:
FullMsgId _itemId;
int _row, _col;
bool _fullDisplayed = true;
// Returns the full text of the corresponding button.
QString buttonText() const;
};
class ReplyKeyboard {
private:
struct Button;
public:
class Style {
public:
Style(const style::BotKeyboardButton &st) : _st(&st) {
}
virtual void startPaint(Painter &p) const = 0;
virtual const style::TextStyle &textStyle() const = 0;
int buttonSkip() const;
int buttonPadding() const;
int buttonHeight() const;
virtual int buttonRadius() const = 0;
virtual void repaint(not_null<const HistoryItem*> item) const = 0;
virtual ~Style() {
}
protected:
virtual void paintButtonBg(Painter &p, const QRect &rect, float64 howMuchOver) const = 0;
virtual void paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageReplyMarkup::Button::Type type) const = 0;
virtual void paintButtonLoading(Painter &p, const QRect &rect) const = 0;
virtual int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const = 0;
private:
const style::BotKeyboardButton *_st;
void paintButton(Painter &p, int outerWidth, const ReplyKeyboard::Button &button, TimeMs ms) const;
friend class ReplyKeyboard;
};
typedef std::unique_ptr<Style> StylePtr;
ReplyKeyboard(const HistoryItem *item, StylePtr &&s);
ReplyKeyboard(const ReplyKeyboard &other) = delete;
ReplyKeyboard &operator=(const ReplyKeyboard &other) = delete;
bool isEnoughSpace(int width, const style::BotKeyboardButton &st) const;
void setStyle(StylePtr &&s);
void resize(int width, int height);
// what width and height will best fit this keyboard
int naturalWidth() const;
int naturalHeight() const;
void paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const;
ClickHandlerPtr getState(QPoint point) const;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active);
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed);
void clearSelection();
void updateMessageId();
private:
void startAnimation(int i, int j, int direction);
friend class Style;
using ReplyMarkupClickHandlerPtr = QSharedPointer<ReplyMarkupClickHandler>;
struct Button {
Text text = { 1 };
QRect rect;
int characters = 0;
float64 howMuchOver = 0.;
HistoryMessageReplyMarkup::Button::Type type;
ReplyMarkupClickHandlerPtr link;
mutable QSharedPointer<Ui::RippleAnimation> ripple;
};
using ButtonRow = QVector<Button>;
using ButtonRows = QVector<ButtonRow>;
struct ButtonCoords {
int i, j;
};
ButtonCoords findButtonCoordsByClickHandler(const ClickHandlerPtr &p);
using Animations = QMap<int, TimeMs>;
void step_selected(TimeMs ms, bool timer);
const HistoryItem *_item;
int _width = 0;
ButtonRows _rows;
Animations _animations;
BasicAnimation _a_selected;
StylePtr _st;
ClickHandlerPtr _savedPressed;
ClickHandlerPtr _savedActive;
mutable QPoint _savedCoords;
};
// Any HistoryItem can have this Component for
// displaying the day mark above the message.
struct HistoryMessageDate : public RuntimeComponent<HistoryMessageDate> {
void init(const QDateTime &date);
int height() const;
void paint(Painter &p, int y, int w) const;
QString _text;
int _width = 0;
};
// Any HistoryItem can have this Component for
// displaying the unread messages bar above the message.
struct HistoryMessageUnreadBar : public RuntimeComponent<HistoryMessageUnreadBar> {
void init(int count);
static int height();
static int marginTop();
void paint(Painter &p, int y, int w) const;
QString _text;
int _width = 0;
// If unread bar is freezed the new messages do not
// increment the counter displayed by this bar.
//
// It happens when we've opened the conversation and
// we've seen the bar and new messages are marked as read
// as soon as they are added to the chat history.
bool _freezed = false;
};
struct MessageGroupId {
using Underlying = uint64;
enum Type : Underlying {
None = 0,
} value;
MessageGroupId(Type value = None) : value(value) {
}
static MessageGroupId FromRaw(Underlying value) {
return static_cast<Type>(value);
}
explicit operator bool() const {
return value != None;
}
friend inline Type value_ordering_helper(MessageGroupId value) {
return value.value;
}
};
struct HistoryMessageGroup : public RuntimeComponent<HistoryMessageGroup> {
MessageGroupId groupId = MessageGroupId::None;
HistoryItem *leader = nullptr;
std::vector<not_null<HistoryItem*>> others;
};
class HistoryWebPage;
// Special type of Component for the channel actions log.
struct HistoryMessageLogEntryOriginal : public RuntimeComponent<HistoryMessageLogEntryOriginal> {
HistoryMessageLogEntryOriginal();
HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other);
HistoryMessageLogEntryOriginal &operator=(HistoryMessageLogEntryOriginal &&other);
~HistoryMessageLogEntryOriginal();
std::unique_ptr<HistoryWebPage> _page;
};
// HistoryMedia has a special owning smart pointer
// which regs/unregs this media to the holding HistoryItem
class HistoryMedia;
class HistoryMediaPtr {
public:
HistoryMediaPtr();
@@ -534,7 +176,10 @@ inline TextSelection shiftSelection(TextSelection selection, const Text &byText)
} // namespace internal
class HistoryItem : public HistoryElement, public RuntimeComposer, public ClickHandlerHost {
class HistoryItem
: public HistoryElement
, public RuntimeComposer
, public ClickHandlerHost {
public:
int resizeGetHeight(int newWidth) {
if (_flags & MTPDmessage_ClientFlag::f_pending_init_dimensions) {
@@ -564,12 +209,7 @@ public:
const base::flat_map<UserId, bool> &changes) {
}
UserData *viaBot() const {
if (auto via = Get<HistoryMessageVia>()) {
return via->_bot;
}
return nullptr;
}
UserData *viaBot() const;
UserData *getMessageBot() const {
if (auto bot = viaBot()) {
return bot;
@@ -584,7 +224,10 @@ public:
bool isLogEntry() const {
return (id > ServerMaxMsgId);
}
void addLogEntryOriginal(WebPageId localId, const QString &label, const TextWithEntities &content);
void addLogEntryOriginal(
WebPageId localId,
const QString &label,
const TextWithEntities &content);
not_null<History*> history() const {
return _history;
@@ -642,28 +285,9 @@ public:
return 0;
}
bool definesReplyKeyboard() const {
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
return false;
}
return true;
}
bool definesReplyKeyboard() const;
MTPDreplyKeyboardMarkup::Flags replyKeyboardFlags() const;
// optimization: don't create markup component for the case
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
return (_flags & MTPDmessage::Flag::f_reply_markup);
}
MTPDreplyKeyboardMarkup::Flags replyKeyboardFlags() const {
Expects(definesReplyKeyboard());
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
return markup->flags;
}
// optimization: don't create markup component for the case
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
return MTPDreplyKeyboardMarkup_ClientFlag::f_zero | 0;
}
bool hasSwitchInlineButton() const {
return _flags & MTPDmessage_ClientFlag::f_has_switch_inline_button;
}
@@ -679,9 +303,6 @@ public:
bool isPost() const {
return _flags & MTPDmessage::Flag::f_post;
}
bool indexInUnreadMentions() const {
return (id > 0);
}
bool isSilent() const {
return _flags & MTPDmessage::Flag::f_silent;
}
@@ -725,11 +346,12 @@ public:
virtual void updateReplyMarkup(const MTPReplyMarkup *markup) {
}
virtual void addToUnreadMentions(AddToUnreadMentionsMethod method) {
virtual void addToUnreadMentions(UnreadMentionType type) {
}
virtual void eraseFromUnreadMentions() {
}
virtual Storage::SharedMediaTypesMask sharedMediaTypes() const;
void indexAsNewItem();
virtual bool hasBubble() const {
return false;
@@ -864,52 +486,17 @@ public:
virtual const HistoryMessage *toHistoryMessage() const { // dynamic_cast optimize
return nullptr;
}
MsgId replyToId() const {
if (auto reply = Get<HistoryMessageReply>()) {
return reply->replyToId();
}
return 0;
}
MsgId replyToId() const;
PeerData *author() const {
return isPost() ? history()->peer : from();
}
QDateTime dateOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_originalDate;
}
return date;
}
PeerData *senderOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_originalSender;
}
auto peer = history()->peer;
return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
}
PeerData *fromOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
if (auto user = forwarded->_originalSender->asUser()) {
return user;
}
}
return from();
}
QString authorOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_originalAuthor;
} else if (auto msgsigned = Get<HistoryMessageSigned>()) {
return msgsigned->_author;
}
return QString();
}
MsgId idOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_originalId;
}
return id;
}
QDateTime dateOriginal() const;
PeerData *senderOriginal() const;
PeerData *fromOriginal() const;
QString authorOriginal() const;
MsgId idOriginal() const;
// count > 0 - creates the unread bar if necessary and
// sets unread messages count if bar is not freezed yet
@@ -939,12 +526,7 @@ public:
setPendingResize();
}
int displayedDateHeight() const {
if (auto date = Get<HistoryMessageDate>()) {
return date->height();
}
return 0;
}
int displayedDateHeight() const;
int marginTop() const;
int marginBottom() const;
bool isAttachedToPrevious() const {
@@ -953,27 +535,18 @@ public:
bool isAttachedToNext() const {
return _flags & MTPDmessage_ClientFlag::f_attach_to_next;
}
bool displayDate() const {
return Has<HistoryMessageDate>();
}
bool displayDate() const;
bool isInOneDayWithPrevious() const {
return !isEmpty() && !displayDate();
}
bool isEmpty() const {
return _text.isEmpty() && !_media && !Has<HistoryMessageLogEntryOriginal>();
}
bool isEmpty() const;
bool isHiddenByGroup() const {
return _flags & MTPDmessage_ClientFlag::f_hidden_by_group;
}
MessageGroupId groupId() const {
if (const auto group = Get<HistoryMessageGroup>()) {
return group->groupId;
}
return MessageGroupId::None;
}
MessageGroupId groupId() const;
bool groupIdValidityChanged();
void validateGroupId() {
// Just ignore the result.
@@ -1089,20 +662,8 @@ protected:
const ReplyKeyboard *inlineReplyKeyboard() const {
return const_cast<HistoryItem*>(this)->inlineReplyKeyboard();
}
HistoryMessageReplyMarkup *inlineReplyMarkup() {
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
return markup;
}
}
return nullptr;
}
ReplyKeyboard *inlineReplyKeyboard() {
if (auto markup = inlineReplyMarkup()) {
return markup->inlineKeyboard.get();
}
return nullptr;
}
HistoryMessageReplyMarkup *inlineReplyMarkup();
ReplyKeyboard *inlineReplyKeyboard();
void invalidateChatsListEntry();
[[nodiscard]] TextSelection skipTextSelection(

View File

@@ -0,0 +1,886 @@
/*
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 "history/history_item_components.h"
#include "lang/lang_keys.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text_options.h"
#include "history/history_service_layout.h"
#include "history/history_message.h"
#include "history/history_media.h"
#include "history/history_media_types.h"
#include "media/media_audio.h"
#include "media/player/media_player_instance.h"
#include "styles/style_widgets.h"
#include "styles/style_history.h"
void HistoryMessageVia::create(UserId userId) {
bot = App::user(peerFromUser(userId));
maxWidth = st::msgServiceNameFont->width(
lng_inline_bot_via(lt_inline_bot, '@' + bot->username));
link = std::make_shared<LambdaClickHandler>([bot = this->bot] {
App::insertBotCommand('@' + bot->username);
});
}
void HistoryMessageVia::resize(int32 availw) const {
if (availw < 0) {
text = QString();
width = 0;
} else {
text = lng_inline_bot_via(lt_inline_bot, '@' + bot->username);
if (availw < maxWidth) {
text = st::msgServiceNameFont->elided(text, availw);
width = st::msgServiceNameFont->width(text);
} else if (width < maxWidth) {
width = maxWidth;
}
}
}
void HistoryMessageSigned::refresh(const QString &date) {
auto time = qsl(", ") + date;
auto name = author;
auto timew = st::msgDateFont->width(time);
auto namew = st::msgDateFont->width(name);
if (timew + namew > st::maxSignatureSize) {
name = st::msgDateFont->elided(author, st::maxSignatureSize - timew);
}
signature.setText(
st::msgDateTextStyle,
name + time,
Ui::NameTextOptions());
}
int HistoryMessageSigned::maxWidth() const {
return signature.maxWidth();
}
void HistoryMessageEdited::refresh(const QString &date, bool displayed) {
const auto prefix = displayed ? (lang(lng_edited) + ' ') : QString();
text.setText(st::msgDateTextStyle, prefix + date, Ui::NameTextOptions());
}
int HistoryMessageEdited::maxWidth() const {
return text.maxWidth();
}
void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
auto phrase = QString();
auto fromChannel = (originalSender->isChannel() && !originalSender->isMegagroup());
if (!originalAuthor.isEmpty()) {
phrase = lng_forwarded_signed(
lt_channel,
App::peerName(originalSender),
lt_user,
originalAuthor);
} else {
phrase = App::peerName(originalSender);
}
if (via) {
if (fromChannel) {
phrase = lng_forwarded_channel_via(
lt_channel,
textcmdLink(1, phrase),
lt_inline_bot,
textcmdLink(2, '@' + via->bot->username));
} else {
phrase = lng_forwarded_via(
lt_user,
textcmdLink(1, phrase),
lt_inline_bot,
textcmdLink(2, '@' + via->bot->username));
}
} else {
if (fromChannel) {
phrase = lng_forwarded_channel(
lt_channel,
textcmdLink(1, phrase));
} else {
phrase = lng_forwarded(
lt_user,
textcmdLink(1, phrase));
}
}
TextParseOptions opts = {
TextParseRichText,
0,
0,
Qt::LayoutDirectionAuto
};
text.setText(st::fwdTextStyle, phrase, opts);
text.setLink(1, fromChannel
? goToMessageClickHandler(originalSender, originalId)
: originalSender->openLink());
if (via) {
text.setLink(2, via->link);
}
}
bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) {
if (!force) {
if (replyToMsg || !replyToMsgId) {
return true;
}
}
if (!replyToMsg) {
replyToMsg = App::histItemById(holder->channelId(), replyToMsgId);
if (replyToMsg) {
if (replyToMsg->isEmpty()) {
// Really it is deleted.
replyToMsg = nullptr;
force = true;
} else {
App::historyRegDependency(holder, replyToMsg);
}
}
}
if (replyToMsg) {
replyToText.setText(
st::messageTextStyle,
TextUtilities::Clean(replyToMsg->inReplyText()),
Ui::DialogTextOptions());
updateName();
replyToLnk = goToMessageClickHandler(replyToMsg);
if (!replyToMsg->Has<HistoryMessageForwarded>()) {
if (auto bot = replyToMsg->viaBot()) {
replyToVia = std::make_unique<HistoryMessageVia>();
replyToVia->create(peerToUser(bot->id));
}
}
} else if (force) {
replyToMsgId = 0;
}
if (force) {
holder->setPendingInitDimensions();
}
return (replyToMsg || !replyToMsgId);
}
void HistoryMessageReply::clearData(HistoryMessage *holder) {
replyToVia = nullptr;
if (replyToMsg) {
App::historyUnregDependency(holder, replyToMsg);
replyToMsg = nullptr;
}
replyToMsgId = 0;
}
bool HistoryMessageReply::isNameUpdated() const {
if (replyToMsg && replyToMsg->author()->nameVersion > replyToVersion) {
updateName();
return true;
}
return false;
}
void HistoryMessageReply::updateName() const {
if (replyToMsg) {
QString name = (replyToVia && replyToMsg->author()->isUser())
? replyToMsg->author()->asUser()->firstName
: App::peerName(replyToMsg->author());
replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions());
replyToVersion = replyToMsg->author()->nameVersion;
bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false;
int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
int32 w = replyToName.maxWidth();
if (replyToVia) {
w += st::msgServiceFont->spacew + replyToVia->maxWidth;
}
maxReplyWidth = previewSkip + qMax(w, qMin(replyToText.maxWidth(), int32(st::maxSignatureSize)));
} else {
maxReplyWidth = st::msgDateFont->width(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message));
}
maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + maxReplyWidth + st::msgReplyPadding.right();
}
void HistoryMessageReply::resize(int width) const {
if (replyToVia) {
bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false;
int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
replyToVia->resize(width - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew);
}
}
void HistoryMessageReply::itemRemoved(HistoryMessage *holder, HistoryItem *removed) {
if (replyToMsg == removed) {
clearData(holder);
holder->setPendingInitDimensions();
}
}
void HistoryMessageReply::paint(Painter &p, const HistoryItem *holder, int x, int y, int w, PaintFlags flags) const {
bool selected = (flags & PaintFlag::Selected), outbg = holder->hasOutLayout();
style::color bar = st::msgImgReplyBarColor;
if (flags & PaintFlag::InBubble) {
bar = (flags & PaintFlag::Selected) ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor);
}
QRect rbar(rtlrect(x + st::msgReplyBarPos.x(), y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), w + 2 * x));
p.fillRect(rbar, bar);
if (w > st::msgReplyBarSkip) {
if (replyToMsg) {
auto hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false;
if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) {
hasPreview = false;
}
auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
if (hasPreview) {
ImagePtr replyPreview = replyToMsg->getMedia()->replyPreview();
if (!replyPreview->isNull()) {
auto to = rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x);
auto previewWidth = replyPreview->width() / cIntRetinaFactor();
auto previewHeight = replyPreview->height() / cIntRetinaFactor();
auto preview = replyPreview->pixSingle(previewWidth, previewHeight, to.width(), to.height(), ImageRoundRadius::Small, RectPart::AllCorners, selected ? &st::msgStickerOverlay : nullptr);
p.drawPixmap(to.x(), to.y(), preview);
}
}
if (w > st::msgReplyBarSkip + previewSkip) {
if (flags & PaintFlag::InBubble) {
p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg));
} else {
p.setPen(st::msgImgReplyBarColor);
}
replyToName.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x);
if (replyToVia && w > st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew) {
p.setFont(st::msgServiceFont);
p.drawText(x + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, replyToVia->text);
}
auto replyToAsMsg = replyToMsg->toHistoryMessage();
if (!(flags & PaintFlag::InBubble)) {
} else if ((replyToAsMsg && replyToAsMsg->emptyText()) || replyToMsg->serviceMsg()) {
p.setPen(outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg));
} else {
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
}
replyToText.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top() + st::msgServiceNameFont->height, w - st::msgReplyBarSkip - previewSkip, w + 2 * x);
}
} else {
p.setFont(st::msgDateFont);
auto &date = outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg);
p.setPen((flags & PaintFlag::InBubble) ? date : st::msgDateImgFg);
p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message), w - st::msgReplyBarSkip));
}
}
}
ReplyMarkupClickHandler::ReplyMarkupClickHandler(
int row,
int column,
FullMsgId context)
: _itemId(context)
, _row(row)
, _column(column) {
}
// Copy to clipboard support.
void ReplyMarkupClickHandler::copyToClipboard() const {
if (auto button = getButton()) {
if (button->type == HistoryMessageMarkupButton::Type::Url) {
auto url = QString::fromUtf8(button->data);
if (!url.isEmpty()) {
QApplication::clipboard()->setText(url);
}
}
}
}
QString ReplyMarkupClickHandler::copyToClipboardContextItemText() const {
if (auto button = getButton()) {
if (button->type == HistoryMessageMarkupButton::Type::Url) {
return lang(lng_context_copy_link);
}
}
return QString();
}
// Finds the corresponding button in the items markup struct.
// If the button is not found it returns nullptr.
// Note: it is possible that we will point to the different button
// than the one was used when constructing the handler, but not a big deal.
const HistoryMessageMarkupButton *ReplyMarkupClickHandler::getButton() const {
if (auto item = App::histItemById(_itemId)) {
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
if (_row < markup->rows.size()) {
auto &row = markup->rows[_row];
if (_column < row.size()) {
return &row[_column];
}
}
}
}
return nullptr;
}
void ReplyMarkupClickHandler::onClickImpl() const {
if (const auto item = App::histItemById(_itemId)) {
App::activateBotCommand(item, _row, _column);
}
}
// Returns the full text of the corresponding button.
QString ReplyMarkupClickHandler::buttonText() const {
if (const auto button = getButton()) {
return button->text;
}
return QString();
}
ReplyKeyboard::Button::Button() = default;
ReplyKeyboard::Button::Button(Button &&other) = default;
ReplyKeyboard::Button &ReplyKeyboard::Button::operator=(
Button &&other) = default;
ReplyKeyboard::Button::~Button() = default;
ReplyKeyboard::ReplyKeyboard(
not_null<const HistoryItem*> item,
std::unique_ptr<Style> &&s)
: _item(item)
, _a_selected(animation(this, &ReplyKeyboard::step_selected))
, _st(std::move(s)) {
if (const auto markup = _item->Get<HistoryMessageReplyMarkup>()) {
const auto context = _item->fullId();
const auto rowCount = int(markup->rows.size());
_rows.reserve(rowCount);
for (auto i = 0; i != rowCount; ++i) {
const auto &row = markup->rows.at(i);
const auto rowSize = int(row.size());
auto newRow = std::vector<Button>();
newRow.reserve(rowSize);
for (auto j = 0; j != rowSize; ++j) {
auto button = Button();
const auto text = row[j].text;
button.type = row.at(j).type;
button.link = std::make_shared<ReplyMarkupClickHandler>(
i,
j,
context);
button.text.setText(
_st->textStyle(),
TextUtilities::SingleLine(text),
_textPlainOptions);
button.characters = text.isEmpty() ? 1 : text.size();
newRow.push_back(std::move(button));
}
_rows.push_back(std::move(newRow));
}
}
}
void ReplyKeyboard::updateMessageId() {
const auto msgId = _item->fullId();
for (const auto &row : _rows) {
for (const auto &button : row) {
button.link->setMessageId(msgId);
}
}
}
void ReplyKeyboard::resize(int width, int height) {
_width = width;
auto markup = _item->Get<HistoryMessageReplyMarkup>();
auto y = 0.;
auto buttonHeight = _rows.empty()
? float64(_st->buttonHeight())
: (float64(height + _st->buttonSkip()) / _rows.size());
for (auto &row : _rows) {
int s = row.size();
int widthForButtons = _width - ((s - 1) * _st->buttonSkip());
int widthForText = widthForButtons;
int widthOfText = 0;
int maxMinButtonWidth = 0;
for_const (auto &button, row) {
widthOfText += qMax(button.text.maxWidth(), 1);
int minButtonWidth = _st->minButtonWidth(button.type);
widthForText -= minButtonWidth;
accumulate_max(maxMinButtonWidth, minButtonWidth);
}
bool exact = (widthForText == widthOfText);
bool enough = (widthForButtons - s * maxMinButtonWidth) >= widthOfText;
float64 x = 0;
for (Button &button : row) {
int buttonw = qMax(button.text.maxWidth(), 1);
float64 textw = buttonw, minw = _st->minButtonWidth(button.type);
float64 w = textw;
if (exact) {
w += minw;
} else if (enough) {
w = (widthForButtons / float64(s));
textw = w - minw;
} else {
textw = (widthForText / float64(s));
w = minw + textw;
accumulate_max(w, 2 * float64(_st->buttonPadding()));
}
int rectx = static_cast<int>(std::floor(x));
int rectw = static_cast<int>(std::floor(x + w)) - rectx;
button.rect = QRect(rectx, qRound(y), rectw, qRound(buttonHeight - _st->buttonSkip()));
if (rtl()) button.rect.setX(_width - button.rect.x() - button.rect.width());
x += w + _st->buttonSkip();
button.link->setFullDisplayed(textw >= buttonw);
}
y += buttonHeight;
}
}
bool ReplyKeyboard::isEnoughSpace(int width, const style::BotKeyboardButton &st) const {
for_const (auto &row, _rows) {
int s = row.size();
int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding);
for_const (auto &button, row) {
widthLeft -= qMax(button.text.maxWidth(), 1);
if (widthLeft < 0) {
if (row.size() > 3) {
return false;
} else {
break;
}
}
}
}
return true;
}
void ReplyKeyboard::setStyle(std::unique_ptr<Style> &&st) {
_st = std::move(st);
}
int ReplyKeyboard::naturalWidth() const {
auto result = 0;
for (const auto &row : _rows) {
auto maxMinButtonWidth = 0;
for (const auto &button : row) {
accumulate_max(
maxMinButtonWidth,
_st->minButtonWidth(button.type));
}
auto rowMaxButtonWidth = 0;
for (const auto &button : row) {
accumulate_max(
rowMaxButtonWidth,
qMax(button.text.maxWidth(), 1) + maxMinButtonWidth);
}
const auto rowSize = int(row.size());
accumulate_max(
result,
rowSize * rowMaxButtonWidth + (rowSize - 1) * _st->buttonSkip());
}
return result;
}
int ReplyKeyboard::naturalHeight() const {
return (_rows.size() - 1) * _st->buttonSkip() + _rows.size() * _st->buttonHeight();
}
void ReplyKeyboard::paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const {
Assert(_st != nullptr);
Assert(_width > 0);
_st->startPaint(p);
for_const (auto &row, _rows) {
for_const (auto &button, row) {
QRect rect(button.rect);
if (rect.y() >= clip.y() + clip.height()) return;
if (rect.y() + rect.height() < clip.y()) continue;
// just ignore the buttons that didn't layout well
if (rect.x() + rect.width() > _width) break;
_st->paintButton(p, outerWidth, button, ms);
}
}
}
ClickHandlerPtr ReplyKeyboard::getState(QPoint point) const {
Assert(_width > 0);
for_const (auto &row, _rows) {
for_const (auto &button, row) {
QRect rect(button.rect);
// just ignore the buttons that didn't layout well
if (rect.x() + rect.width() > _width) break;
if (rect.contains(point)) {
_savedCoords = point;
return button.link;
}
}
}
return ClickHandlerPtr();
}
void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (!p) return;
_savedActive = active ? p : ClickHandlerPtr();
auto coords = findButtonCoordsByClickHandler(p);
if (coords.i >= 0 && _savedPressed != p) {
startAnimation(coords.i, coords.j, active ? 1 : -1);
}
}
ReplyKeyboard::ButtonCoords ReplyKeyboard::findButtonCoordsByClickHandler(const ClickHandlerPtr &p) {
for (int i = 0, rows = _rows.size(); i != rows; ++i) {
auto &row = _rows[i];
for (int j = 0, cols = row.size(); j != cols; ++j) {
if (row[j].link == p) {
return { i, j };
}
}
}
return { -1, -1 };
}
void ReplyKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
if (!p) return;
_savedPressed = pressed ? p : ClickHandlerPtr();
auto coords = findButtonCoordsByClickHandler(p);
if (coords.i >= 0) {
auto &button = _rows[coords.i][coords.j];
if (pressed) {
if (!button.ripple) {
auto mask = Ui::RippleAnimation::roundRectMask(
button.rect.size(),
_st->buttonRadius());
button.ripple = std::make_unique<Ui::RippleAnimation>(
_st->_st->ripple,
std::move(mask),
[this] { _st->repaint(_item); });
}
button.ripple->add(_savedCoords - button.rect.topLeft());
} else {
if (button.ripple) {
button.ripple->lastStop();
}
if (_savedActive != p) {
startAnimation(coords.i, coords.j, -1);
}
}
}
}
void ReplyKeyboard::startAnimation(int i, int j, int direction) {
auto notStarted = _animations.empty();
int indexForAnimation = (i * MatrixRowShift + j + 1) * direction;
_animations.remove(-indexForAnimation);
if (!_animations.contains(indexForAnimation)) {
_animations.emplace(indexForAnimation, getms());
}
if (notStarted && !_a_selected.animating()) {
_a_selected.start();
}
}
void ReplyKeyboard::step_selected(TimeMs ms, bool timer) {
for (auto i = _animations.begin(); i != _animations.end();) {
const auto index = std::abs(i->first) - 1;
const auto row = (index / MatrixRowShift);
const auto col = index % MatrixRowShift;
const auto dt = float64(ms - i->second) / st::botKbDuration;
if (dt >= 1) {
_rows[row][col].howMuchOver = (i->first > 0) ? 1 : 0;
i = _animations.erase(i);
} else {
_rows[row][col].howMuchOver = (i->first > 0) ? dt : (1 - dt);
++i;
}
}
if (timer) _st->repaint(_item);
if (_animations.empty()) {
_a_selected.stop();
}
}
void ReplyKeyboard::clearSelection() {
for (const auto [relativeIndex, time] : _animations) {
const auto index = std::abs(relativeIndex) - 1;
const auto row = (index / MatrixRowShift);
const auto col = index % MatrixRowShift;
_rows[row][col].howMuchOver = 0;
}
_animations.clear();
_a_selected.stop();
}
int ReplyKeyboard::Style::buttonSkip() const {
return _st->margin;
}
int ReplyKeyboard::Style::buttonPadding() const {
return _st->padding;
}
int ReplyKeyboard::Style::buttonHeight() const {
return _st->height;
}
void ReplyKeyboard::Style::paintButton(
Painter &p,
int outerWidth,
const ReplyKeyboard::Button &button,
TimeMs ms) const {
const QRect &rect = button.rect;
paintButtonBg(p, rect, button.howMuchOver);
if (button.ripple) {
button.ripple->paint(p, rect.x(), rect.y(), outerWidth, ms);
if (button.ripple->empty()) {
button.ripple.reset();
}
}
paintButtonIcon(p, rect, outerWidth, button.type);
if (button.type == HistoryMessageMarkupButton::Type::Callback
|| button.type == HistoryMessageMarkupButton::Type::Game) {
if (auto data = button.link->getButton()) {
if (data->requestId) {
paintButtonLoading(p, rect);
}
}
}
int tx = rect.x(), tw = rect.width();
if (tw >= st::botKbStyle.font->elidew + _st->padding * 2) {
tx += _st->padding;
tw -= _st->padding * 2;
} else if (tw > st::botKbStyle.font->elidew) {
tx += (tw - st::botKbStyle.font->elidew) / 2;
tw = st::botKbStyle.font->elidew;
}
button.text.drawElided(p, tx, rect.y() + _st->textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top);
}
void HistoryMessageReplyMarkup::createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v) {
if (v.isEmpty()) {
rows.clear();
return;
}
rows.reserve(v.size());
for_const (auto &row, v) {
switch (row.type()) {
case mtpc_keyboardButtonRow: {
auto &r = row.c_keyboardButtonRow();
auto &b = r.vbuttons.v;
if (!b.isEmpty()) {
auto buttonRow = std::vector<Button>();
buttonRow.reserve(b.size());
for_const (auto &button, b) {
switch (button.type()) {
case mtpc_keyboardButton: {
buttonRow.push_back({ Button::Type::Default, qs(button.c_keyboardButton().vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonCallback: {
auto &buttonData = button.c_keyboardButtonCallback();
buttonRow.push_back({ Button::Type::Callback, qs(buttonData.vtext), qba(buttonData.vdata), 0 });
} break;
case mtpc_keyboardButtonRequestGeoLocation: {
buttonRow.push_back({ Button::Type::RequestLocation, qs(button.c_keyboardButtonRequestGeoLocation().vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonRequestPhone: {
buttonRow.push_back({ Button::Type::RequestPhone, qs(button.c_keyboardButtonRequestPhone().vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonUrl: {
auto &buttonData = button.c_keyboardButtonUrl();
buttonRow.push_back({ Button::Type::Url, qs(buttonData.vtext), qba(buttonData.vurl), 0 });
} break;
case mtpc_keyboardButtonSwitchInline: {
auto &buttonData = button.c_keyboardButtonSwitchInline();
auto buttonType = buttonData.is_same_peer() ? Button::Type::SwitchInlineSame : Button::Type::SwitchInline;
buttonRow.push_back({ buttonType, qs(buttonData.vtext), qba(buttonData.vquery), 0 });
if (buttonType == Button::Type::SwitchInline) {
// Optimization flag.
// Fast check on all new messages if there is a switch button to auto-click it.
flags |= MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button;
}
} break;
case mtpc_keyboardButtonGame: {
auto &buttonData = button.c_keyboardButtonGame();
buttonRow.push_back({ Button::Type::Game, qs(buttonData.vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonBuy: {
auto &buttonData = button.c_keyboardButtonBuy();
buttonRow.push_back({ Button::Type::Buy, qs(buttonData.vtext), QByteArray(), 0 });
}
}
}
if (!buttonRow.empty()) {
rows.push_back(std::move(buttonRow));
}
}
} break;
}
}
}
void HistoryMessageReplyMarkup::create(const MTPReplyMarkup &markup) {
flags = 0;
rows.clear();
inlineKeyboard = nullptr;
switch (markup.type()) {
case mtpc_replyKeyboardMarkup: {
auto &d = markup.c_replyKeyboardMarkup();
flags = d.vflags.v;
createFromButtonRows(d.vrows.v);
} break;
case mtpc_replyInlineMarkup: {
auto &d = markup.c_replyInlineMarkup();
flags = MTPDreplyKeyboardMarkup::Flags(0) | MTPDreplyKeyboardMarkup_ClientFlag::f_inline;
createFromButtonRows(d.vrows.v);
} break;
case mtpc_replyKeyboardHide: {
auto &d = markup.c_replyKeyboardHide();
flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_zero;
} break;
case mtpc_replyKeyboardForceReply: {
auto &d = markup.c_replyKeyboardForceReply();
flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply;
} break;
}
}
void HistoryMessageReplyMarkup::create(
const HistoryMessageReplyMarkup &markup) {
flags = markup.flags;
inlineKeyboard = nullptr;
rows.clear();
for (const auto &row : markup.rows) {
auto buttonRow = std::vector<Button>();
buttonRow.reserve(row.size());
for (const auto &button : row) {
buttonRow.push_back({ button.type, button.text, button.data, 0 });
}
if (!buttonRow.empty()) {
rows.push_back(std::move(buttonRow));
}
}
}
void HistoryMessageUnreadBar::init(int count) {
if (_freezed) return;
_text = lng_unread_bar(lt_count, count);
_width = st::semiboldFont->width(_text);
}
int HistoryMessageUnreadBar::height() {
return st::historyUnreadBarHeight + st::historyUnreadBarMargin;
}
int HistoryMessageUnreadBar::marginTop() {
return st::lineWidth + st::historyUnreadBarMargin;
}
void HistoryMessageUnreadBar::paint(Painter &p, int y, int w) const {
p.fillRect(0, y + marginTop(), w, height() - marginTop() - st::lineWidth, st::historyUnreadBarBg);
p.fillRect(0, y + height() - st::lineWidth, w, st::lineWidth, st::historyUnreadBarBorder);
p.setFont(st::historyUnreadBarFont);
p.setPen(st::historyUnreadBarFg);
int left = st::msgServiceMargin.left();
int maxwidth = w;
if (Adaptive::ChatWide()) {
maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
}
w = maxwidth;
p.drawText((w - _width) / 2, y + marginTop() + (st::historyUnreadBarHeight - 2 * st::lineWidth - st::historyUnreadBarFont->height) / 2 + st::historyUnreadBarFont->ascent, _text);
}
void HistoryMessageDate::init(const QDateTime &date) {
_text = langDayOfMonthFull(date.date());
_width = st::msgServiceFont->width(_text);
}
int HistoryMessageDate::height() const {
return st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom() + st::msgServiceMargin.bottom();
}
void HistoryMessageDate::paint(Painter &p, int y, int w) const {
HistoryLayout::ServiceMessagePainter::paintDate(p, _text, _width, y, w);
}
HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal() = default;
HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other) : _page(std::move(other._page)) {
}
HistoryMessageLogEntryOriginal &HistoryMessageLogEntryOriginal::operator=(HistoryMessageLogEntryOriginal &&other) {
_page = std::move(other._page);
return *this;
}
HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default;
HistoryDocumentCaptioned::HistoryDocumentCaptioned()
: _caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) {
}
HistoryDocumentVoicePlayback::HistoryDocumentVoicePlayback(const HistoryDocument *that)
: a_progress(0., 0.)
, _a_progress(animation(const_cast<HistoryDocument*>(that), &HistoryDocument::step_voiceProgress)) {
}
void HistoryDocumentVoice::ensurePlayback(const HistoryDocument *that) const {
if (!_playback) {
_playback = std::make_unique<HistoryDocumentVoicePlayback>(that);
}
}
void HistoryDocumentVoice::checkPlaybackFinished() const {
if (_playback && !_playback->_a_progress.animating()) {
_playback.reset();
}
}
void HistoryDocumentVoice::startSeeking() {
_seeking = true;
_seekingCurrent = _seekingStart;
Media::Player::instance()->startSeeking(AudioMsgId::Type::Voice);
}
void HistoryDocumentVoice::stopSeeking() {
_seeking = false;
Media::Player::instance()->stopSeeking(AudioMsgId::Type::Voice);
}

View File

@@ -0,0 +1,477 @@
/*
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 "history/history_item.h"
#include "base/value_ordering.h"
class HistoryDocument;
class HistoryWebPage;
struct MessageGroupId {
using Underlying = uint64;
enum Type : Underlying {
None = 0,
} value;
MessageGroupId(Type value = None) : value(value) {
}
static MessageGroupId FromRaw(Underlying value) {
return static_cast<Type>(value);
}
explicit operator bool() const {
return value != None;
}
Underlying raw() const {
return static_cast<Underlying>(value);
}
friend inline Type value_ordering_helper(MessageGroupId value) {
return value.value;
}
};
struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia> {
void create(UserId userId);
void resize(int32 availw) const;
UserData *bot = nullptr;
mutable QString text;
mutable int width = 0;
mutable int maxWidth = 0;
ClickHandlerPtr link;
};
struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews> {
QString _viewsText;
int _views = 0;
int _viewsWidth = 0;
};
struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned> {
void refresh(const QString &date);
int maxWidth() const;
QString author;
Text signature;
};
struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited> {
void refresh(const QString &date, bool displayed);
int maxWidth() const;
QDateTime date;
Text text;
};
struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded> {
void create(const HistoryMessageVia *via) const;
QDateTime originalDate;
PeerData *originalSender = nullptr;
QString originalAuthor;
MsgId originalId = 0;
mutable Text text = { 1 };
PeerData *savedFromPeer = nullptr;
MsgId savedFromMsgId = 0;
};
struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply> {
HistoryMessageReply() = default;
HistoryMessageReply(const HistoryMessageReply &other) = delete;
HistoryMessageReply(HistoryMessageReply &&other) = delete;
HistoryMessageReply &operator=(const HistoryMessageReply &other) = delete;
HistoryMessageReply &operator=(HistoryMessageReply &&other) {
replyToMsgId = other.replyToMsgId;
std::swap(replyToMsg, other.replyToMsg);
replyToLnk = std::move(other.replyToLnk);
replyToName = std::move(other.replyToName);
replyToText = std::move(other.replyToText);
replyToVersion = other.replyToVersion;
maxReplyWidth = other.maxReplyWidth;
replyToVia = std::move(other.replyToVia);
return *this;
}
~HistoryMessageReply() {
// clearData() should be called by holder.
Expects(replyToMsg == nullptr);
Expects(replyToVia == nullptr);
}
bool updateData(HistoryMessage *holder, bool force = false);
// Must be called before destructor.
void clearData(HistoryMessage *holder);
bool isNameUpdated() const;
void updateName() const;
void resize(int width) const;
void itemRemoved(HistoryMessage *holder, HistoryItem *removed);
enum class PaintFlag {
InBubble = (1 << 0),
Selected = (1 << 1),
};
using PaintFlags = base::flags<PaintFlag>;
friend inline constexpr auto is_flag_type(PaintFlag) { return true; };
void paint(
Painter &p,
const HistoryItem *holder,
int x,
int y,
int w,
PaintFlags flags) const;
MsgId replyToId() const {
return replyToMsgId;
}
int replyToWidth() const {
return maxReplyWidth;
}
ClickHandlerPtr replyToLink() const {
return replyToLnk;
}
MsgId replyToMsgId = 0;
HistoryItem *replyToMsg = nullptr;
ClickHandlerPtr replyToLnk;
mutable Text replyToName, replyToText;
mutable int replyToVersion = 0;
mutable int maxReplyWidth = 0;
std::unique_ptr<HistoryMessageVia> replyToVia;
int toWidth = 0;
};
struct HistoryMessageMarkupButton {
enum class Type {
Default,
Url,
Callback,
RequestPhone,
RequestLocation,
SwitchInline,
SwitchInlineSame,
Game,
Buy,
};
Type type;
QString text;
QByteArray data;
mutable mtpRequestId requestId;
};
struct HistoryMessageReplyMarkup : public RuntimeComponent<HistoryMessageReplyMarkup> {
using Button = HistoryMessageMarkupButton;
HistoryMessageReplyMarkup() = default;
HistoryMessageReplyMarkup(MTPDreplyKeyboardMarkup::Flags f) : flags(f) {
}
void create(const MTPReplyMarkup &markup);
void create(const HistoryMessageReplyMarkup &markup);
std::vector<std::vector<Button>> rows;
MTPDreplyKeyboardMarkup::Flags flags = 0;
std::unique_ptr<ReplyKeyboard> inlineKeyboard;
// If >= 0 it holds the y coord of the inlineKeyboard before the last edition.
int oldTop = -1;
private:
void createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v);
};
class ReplyMarkupClickHandler : public LeftButtonClickHandler {
public:
ReplyMarkupClickHandler(int row, int column, FullMsgId context);
QString tooltip() const override {
return _fullDisplayed ? QString() : buttonText();
}
void setFullDisplayed(bool full) {
_fullDisplayed = full;
}
// Copy to clipboard support.
void copyToClipboard() const override;
QString copyToClipboardContextItemText() const override;
// Finds the corresponding button in the items markup struct.
// If the button is not found it returns nullptr.
// Note: it is possible that we will point to the different button
// than the one was used when constructing the handler, but not a big deal.
const HistoryMessageMarkupButton *getButton() const;
// We hold only FullMsgId, not HistoryItem*, because all click handlers
// are activated async and the item may be already destroyed.
void setMessageId(const FullMsgId &msgId) {
_itemId = msgId;
}
protected:
void onClickImpl() const override;
private:
FullMsgId _itemId;
int _row = 0;
int _column = 0;
bool _fullDisplayed = true;
// Returns the full text of the corresponding button.
QString buttonText() const;
};
class ReplyKeyboard {
private:
struct Button;
public:
class Style {
public:
Style(const style::BotKeyboardButton &st) : _st(&st) {
}
virtual void startPaint(Painter &p) const = 0;
virtual const style::TextStyle &textStyle() const = 0;
int buttonSkip() const;
int buttonPadding() const;
int buttonHeight() const;
virtual int buttonRadius() const = 0;
virtual void repaint(not_null<const HistoryItem*> item) const = 0;
virtual ~Style() {
}
protected:
virtual void paintButtonBg(
Painter &p,
const QRect &rect,
float64 howMuchOver) const = 0;
virtual void paintButtonIcon(
Painter &p,
const QRect &rect,
int outerWidth,
HistoryMessageMarkupButton::Type type) const = 0;
virtual void paintButtonLoading(
Painter &p,
const QRect &rect) const = 0;
virtual int minButtonWidth(
HistoryMessageMarkupButton::Type type) const = 0;
private:
const style::BotKeyboardButton *_st;
void paintButton(Painter &p, int outerWidth, const ReplyKeyboard::Button &button, TimeMs ms) const;
friend class ReplyKeyboard;
};
ReplyKeyboard(
not_null<const HistoryItem*> item,
std::unique_ptr<Style> &&s);
ReplyKeyboard(const ReplyKeyboard &other) = delete;
ReplyKeyboard &operator=(const ReplyKeyboard &other) = delete;
bool isEnoughSpace(int width, const style::BotKeyboardButton &st) const;
void setStyle(std::unique_ptr<Style> &&s);
void resize(int width, int height);
// what width and height will best fit this keyboard
int naturalWidth() const;
int naturalHeight() const;
void paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const;
ClickHandlerPtr getState(QPoint point) const;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active);
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed);
void clearSelection();
void updateMessageId();
private:
friend class Style;
struct Button {
Button();
Button(Button &&other);
Button &operator=(Button &&other);
~Button();
Text text = { 1 };
QRect rect;
int characters = 0;
float64 howMuchOver = 0.;
HistoryMessageMarkupButton::Type type;
std::shared_ptr<ReplyMarkupClickHandler> link;
mutable std::unique_ptr<Ui::RippleAnimation> ripple;
};
struct ButtonCoords {
int i, j;
};
void startAnimation(int i, int j, int direction);
ButtonCoords findButtonCoordsByClickHandler(const ClickHandlerPtr &p);
void step_selected(TimeMs ms, bool timer);
const not_null<const HistoryItem*> _item;
int _width = 0;
std::vector<std::vector<Button>> _rows;
base::flat_map<int, TimeMs> _animations;
BasicAnimation _a_selected;
std::unique_ptr<Style> _st;
ClickHandlerPtr _savedPressed;
ClickHandlerPtr _savedActive;
mutable QPoint _savedCoords;
};
// Any HistoryItem can have this Component for
// displaying the day mark above the message.
struct HistoryMessageDate : public RuntimeComponent<HistoryMessageDate> {
void init(const QDateTime &date);
int height() const;
void paint(Painter &p, int y, int w) const;
QString _text;
int _width = 0;
};
// Any HistoryItem can have this Component for
// displaying the unread messages bar above the message.
struct HistoryMessageUnreadBar : public RuntimeComponent<HistoryMessageUnreadBar> {
void init(int count);
static int height();
static int marginTop();
void paint(Painter &p, int y, int w) const;
QString _text;
int _width = 0;
// If unread bar is freezed the new messages do not
// increment the counter displayed by this bar.
//
// It happens when we've opened the conversation and
// we've seen the bar and new messages are marked as read
// as soon as they are added to the chat history.
bool _freezed = false;
};
struct HistoryMessageGroup : public RuntimeComponent<HistoryMessageGroup> {
MessageGroupId groupId = MessageGroupId::None;
HistoryItem *leader = nullptr;
std::vector<not_null<HistoryItem*>> others;
};
// Special type of Component for the channel actions log.
struct HistoryMessageLogEntryOriginal : public RuntimeComponent<HistoryMessageLogEntryOriginal> {
HistoryMessageLogEntryOriginal();
HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other);
HistoryMessageLogEntryOriginal &operator=(HistoryMessageLogEntryOriginal &&other);
~HistoryMessageLogEntryOriginal();
std::unique_ptr<HistoryWebPage> _page;
};
struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed> {
ClickHandlerPtr _linksavel, _linkcancell;
int _thumbw = 0;
mutable int _linkw = 0;
mutable QString _link;
};
struct HistoryDocumentCaptioned : public RuntimeComponent<HistoryDocumentCaptioned> {
HistoryDocumentCaptioned();
Text _caption;
};
struct HistoryDocumentNamed : public RuntimeComponent<HistoryDocumentNamed> {
QString _name;
int _namew = 0;
};
struct HistoryDocumentVoicePlayback {
HistoryDocumentVoicePlayback(const HistoryDocument *that);
int32 _position = 0;
anim::value a_progress;
BasicAnimation _a_progress;
};
class HistoryDocumentVoice : public RuntimeComponent<HistoryDocumentVoice> {
// We don't use float64 because components should align to pointer even on 32bit systems.
static constexpr float64 kFloatToIntMultiplier = 65536.;
public:
void ensurePlayback(const HistoryDocument *interfaces) const;
void checkPlaybackFinished() const;
mutable std::unique_ptr<HistoryDocumentVoicePlayback> _playback;
std::shared_ptr<VoiceSeekClickHandler> _seekl;
mutable int _lastDurationMs = 0;
bool seeking() const {
return _seeking;
}
void startSeeking();
void stopSeeking();
float64 seekingStart() const {
return _seekingStart / kFloatToIntMultiplier;
}
void setSeekingStart(float64 seekingStart) const {
_seekingStart = qRound(seekingStart * kFloatToIntMultiplier);
}
float64 seekingCurrent() const {
return _seekingCurrent / kFloatToIntMultiplier;
}
void setSeekingCurrent(float64 seekingCurrent) {
_seekingCurrent = qRound(seekingCurrent * kFloatToIntMultiplier);
}
private:
bool _seeking = false;
mutable int _seekingStart = 0;
mutable int _seekingCurrent = 0;
};

View File

@@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
struct HistoryMessageEdited;
namespace base {
template <typename Enum>
class enum_mask;
@@ -75,7 +77,7 @@ public:
return false;
}
virtual void initDimensions() = 0;
virtual void updateMessageId() {
virtual void refreshParentId(not_null<HistoryItem*> realParent) {
}
virtual int resizeGetHeight(int width) {
_width = qMin(width, _maxw);

View File

@@ -20,33 +20,15 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "history/history_media_grouped.h"
#include "history/history_item_components.h"
#include "history/history_media_types.h"
#include "history/history_message.h"
#include "storage/storage_shared_media.h"
#include "lang/lang_keys.h"
#include "ui/grouped_layout.h"
#include "ui/text_options.h"
#include "styles/style_history.h"
namespace {
RectParts GetCornersFromSides(RectParts sides) {
const auto convert = [&](
RectPart side1,
RectPart side2,
RectPart corner) {
return ((sides & side1) && (sides & side2))
? corner
: RectPart::None;
};
return RectPart::None
| convert(RectPart::Top, RectPart::Left, RectPart::TopLeft)
| convert(RectPart::Top, RectPart::Right, RectPart::TopRight)
| convert(RectPart::Bottom, RectPart::Left, RectPart::BottomLeft)
| convert(RectPart::Bottom, RectPart::Right, RectPart::BottomRight);
}
} // namespace
HistoryGroupedMedia::Element::Element(not_null<HistoryItem*> item)
: item(item) {
}
@@ -61,6 +43,12 @@ HistoryGroupedMedia::HistoryGroupedMedia(
Ensures(result);
}
std::unique_ptr<HistoryMedia> HistoryGroupedMedia::clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const {
return main()->clone(newParent, realParent);
}
void HistoryGroupedMedia::initDimensions() {
if (_caption.hasSkipBlock()) {
_caption.setSkipBlock(
@@ -76,7 +64,7 @@ void HistoryGroupedMedia::initDimensions() {
sizes.push_back(media->sizeForGrouping());
}
const auto layout = Data::LayoutMediaGroup(
const auto layout = Ui::LayoutMediaGroup(
sizes,
st::historyGroupWidthMax,
st::historyGroupWidthMin,
@@ -102,14 +90,14 @@ void HistoryGroupedMedia::initDimensions() {
}
int HistoryGroupedMedia::resizeGetHeight(int width) {
_width = width;
_width = std::min(width, _maxw);
_height = 0;
if (_width < st::historyGroupWidthMin) {
return _height;
}
const auto initialSpacing = st::historyGroupSkip;
const auto factor = width / float64(_maxw);
const auto factor = _width / float64(_maxw);
const auto scale = [&](int value) {
return int(std::round(value * factor));
};
@@ -142,7 +130,7 @@ int HistoryGroupedMedia::resizeGetHeight(int width) {
if (!_caption.isEmpty()) {
const auto captionw = _width - st::msgPadding.left() - st::msgPadding.right();
_height += st::mediaPadding.bottom() + st::mediaCaptionSkip + _caption.countHeight(captionw);
_height += st::mediaCaptionSkip + _caption.countHeight(captionw);
if (isBubbleBottom()) {
_height += st::msgPadding.bottom();
}
@@ -151,6 +139,13 @@ int HistoryGroupedMedia::resizeGetHeight(int width) {
return _height;
}
void HistoryGroupedMedia::refreshParentId(
not_null<HistoryItem*> realParent) {
for (const auto &element : _elements) {
element.content->refreshParentId(element.item);
}
}
void HistoryGroupedMedia::draw(
Painter &p,
const QRect &clip,
@@ -163,7 +158,7 @@ void HistoryGroupedMedia::draw(
: IsGroupItemSelection(selection, i)
? FullSelection
: TextSelection();
auto corners = GetCornersFromSides(element.sides);
auto corners = Ui::GetCornersFromSides(element.sides);
if (!isBubbleTop()) {
corners &= ~(RectPart::TopLeft | RectPart::TopRight);
}
@@ -401,6 +396,23 @@ Storage::SharedMediaTypesMask HistoryGroupedMedia::sharedMediaTypes() const {
return main()->sharedMediaTypes();
}
void HistoryGroupedMedia::updateSentMedia(const MTPMessageMedia &media) {
return main()->updateSentMedia(media);
}
bool HistoryGroupedMedia::needReSetInlineResultMedia(
const MTPMessageMedia &media) {
return main()->needReSetInlineResultMedia(media);
}
PhotoData *HistoryGroupedMedia::getPhoto() const {
return main()->getPhoto();
}
DocumentData *HistoryGroupedMedia::getDocument() const {
return main()->getDocument();
}
HistoryMessageEdited *HistoryGroupedMedia::displayedEditBadge() const {
if (!_caption.isEmpty()) {
return _elements.front().item->Get<HistoryMessageEdited>();
@@ -430,7 +442,7 @@ void HistoryGroupedMedia::updateNeedBubbleState() {
_caption.setText(
st::messageTextStyle,
captionText.text + _parent->skipBlock(),
itemTextNoMonoOptions(_parent));
Ui::ItemTextNoMonoOptions(_parent));
_needBubble = computeNeedBubble();
}
@@ -438,6 +450,10 @@ bool HistoryGroupedMedia::needsBubble() const {
return _needBubble;
}
bool HistoryGroupedMedia::canEditCaption() const {
return main()->canEditCaption();
}
bool HistoryGroupedMedia::computeNeedBubble() const {
if (!_caption.isEmpty()) {
return true;

View File

@@ -34,13 +34,14 @@ public:
return MediaTypeGrouped;
}
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Unexpected("Clone HistoryGroupedMedia.");
}
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override;
void initDimensions() override;
int resizeGetHeight(int width) override;
void refreshParentId(not_null<HistoryItem*> realParent) override;
void updateSentMedia(const MTPMessageMedia &media) override;
bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
void draw(
Painter &p,
@@ -65,12 +66,8 @@ public:
return !_caption.isEmpty();
}
PhotoData *getPhoto() const override {
return main()->getPhoto();
}
DocumentData *getDocument() const override {
return main()->getDocument();
}
PhotoData *getPhoto() const override;
DocumentData *getDocument() const override;
QString notificationText() const override;
QString inDialogsText() const override;
@@ -111,6 +108,7 @@ public:
bool customInfoLayout() const override {
return _caption.isEmpty();
}
bool canEditCaption() const override;
bool allowsFastShare() const override {
return true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "data/data_web_page.h"
#include "data/data_game.h"
class ReplyMarkupClickHandler;
struct HistoryDocumentNamed;
struct HistoryMessageVia;
struct HistoryMessageReply;
struct HistoryMessageForwarded;
namespace Media {
namespace Clip {
class Playback;
@@ -37,7 +43,6 @@ namespace Ui {
class EmptyUserpic;
} // namespace Ui
void HistoryInitMedia();
TextWithEntities WithCaptionSelectedText(
const QString &attachType,
const Text &caption,
@@ -63,6 +68,8 @@ public:
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
void refreshParentId(not_null<HistoryItem*> realParent) override;
bool allowsFastShare() const override {
return true;
}
@@ -70,30 +77,35 @@ public:
~HistoryFileMedia();
protected:
ClickHandlerPtr _openl, _savel, _cancell;
void setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell);
using FileClickHandlerPtr = std::shared_ptr<FileClickHandler>;
FileClickHandlerPtr _openl, _savel, _cancell;
void setLinks(
FileClickHandlerPtr &&openl,
FileClickHandlerPtr &&savel,
FileClickHandlerPtr &&cancell);
void setDocumentLinks(
not_null<DocumentData*> document,
not_null<HistoryItem*> realParent,
bool inlinegif = false) {
ClickHandlerPtr open, save;
FileClickHandlerPtr open, save;
const auto context = realParent->fullId();
if (inlinegif) {
open = MakeShared<GifOpenClickHandler>(document, context);
open = std::make_shared<GifOpenClickHandler>(document, context);
} else {
open = MakeShared<DocumentOpenClickHandler>(document, context);
open = std::make_shared<DocumentOpenClickHandler>(document, context);
}
if (inlinegif) {
save = MakeShared<GifOpenClickHandler>(document, context);
save = std::make_shared<GifOpenClickHandler>(document, context);
} else if (document->isVoiceMessage()) {
save = MakeShared<DocumentOpenClickHandler>(document, context);
save = std::make_shared<DocumentOpenClickHandler>(document, context);
} else {
save = MakeShared<DocumentSaveClickHandler>(document, context);
save = std::make_shared<DocumentSaveClickHandler>(document, context);
}
setLinks(
std::move(open),
std::move(save),
MakeShared<DocumentCancelClickHandler>(document, context));
std::make_shared<DocumentCancelClickHandler>(document, context));
}
// >= 0 will contain download / upload string, _statusSize = loaded bytes
@@ -250,15 +262,9 @@ public:
}
protected:
float64 dataProgress() const override {
return _data->progress();
}
bool dataFinished() const override {
return !_data->loading() && !_data->uploading();
}
bool dataLoaded() const override {
return _data->loaded();
}
float64 dataProgress() const override;
bool dataFinished() const override;
bool dataLoaded() const override;
private:
void validateGroupedCache(
@@ -370,15 +376,9 @@ public:
}
protected:
float64 dataProgress() const override {
return _data->progress();
}
bool dataFinished() const override {
return !_data->loading() && !_data->uploading();
}
bool dataLoaded() const override {
return _data->loaded();
}
float64 dataProgress() const override;
bool dataFinished() const override;
bool dataLoaded() const override;
private:
void validateGroupedCache(
@@ -395,68 +395,6 @@ private:
};
struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed> {
ClickHandlerPtr _linksavel, _linkcancell;
int _thumbw = 0;
mutable int _linkw = 0;
mutable QString _link;
};
struct HistoryDocumentCaptioned : public RuntimeComponent<HistoryDocumentCaptioned> {
HistoryDocumentCaptioned();
Text _caption;
};
struct HistoryDocumentNamed : public RuntimeComponent<HistoryDocumentNamed> {
QString _name;
int _namew = 0;
};
class HistoryDocument;
struct HistoryDocumentVoicePlayback {
HistoryDocumentVoicePlayback(const HistoryDocument *that);
int32 _position = 0;
anim::value a_progress;
BasicAnimation _a_progress;
};
class HistoryDocumentVoice : public RuntimeComponent<HistoryDocumentVoice> {
// We don't use float64 because components should align to pointer even on 32bit systems.
static constexpr float64 kFloatToIntMultiplier = 65536.;
public:
void ensurePlayback(const HistoryDocument *interfaces) const;
void checkPlaybackFinished() const;
mutable std::unique_ptr<HistoryDocumentVoicePlayback> _playback;
QSharedPointer<VoiceSeekClickHandler> _seekl;
mutable int _lastDurationMs = 0;
bool seeking() const {
return _seeking;
}
void startSeeking();
void stopSeeking();
float64 seekingStart() const {
return _seekingStart / kFloatToIntMultiplier;
}
void setSeekingStart(float64 seekingStart) const {
_seekingStart = qRound(seekingStart * kFloatToIntMultiplier);
}
float64 seekingCurrent() const {
return _seekingCurrent / kFloatToIntMultiplier;
}
void setSeekingCurrent(float64 seekingCurrent) {
_seekingCurrent = qRound(seekingCurrent * kFloatToIntMultiplier);
}
private:
bool _seeking = false;
mutable int _seekingStart = 0;
mutable int _seekingCurrent = 0;
};
class HistoryDocument : public HistoryFileMedia, public RuntimeComposer {
public:
HistoryDocument(
@@ -490,22 +428,10 @@ public:
void updatePressed(QPoint point) override;
[[nodiscard]] TextSelection adjustSelection(
TextSelection selection,
TextSelectType type) const override {
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.adjustSelection(selection, type);
}
return selection;
}
uint16 fullSelectionLength() const override {
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.length();
}
return 0;
}
bool hasTextForCopy() const override {
return Has<HistoryDocumentCaptioned>();
}
TextSelection selection,
TextSelectType type) const override;
uint16 fullSelectionLength() const override;
bool hasTextForCopy() const override;
QString notificationText() const override;
QString inDialogsText() const override;
@@ -534,12 +460,7 @@ public:
}
ImagePtr replyPreview() override;
TextWithEntities getCaption() const override {
if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.originalTextWithEntities();
}
return TextWithEntities();
}
TextWithEntities getCaption() const override;
bool needsBubble() const override {
return true;
}
@@ -559,15 +480,9 @@ public:
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
protected:
float64 dataProgress() const override {
return _data->progress();
}
bool dataFinished() const override {
return !_data->loading() && !_data->uploading();
}
bool dataLoaded() const override {
return _data->loaded();
}
float64 dataProgress() const override;
bool dataFinished() const override;
bool dataLoaded() const override;
private:
void createComponents(bool caption);
@@ -686,10 +601,11 @@ protected:
}
private:
int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const;
int additionalWidth() const {
return additionalWidth(_parent->Get<HistoryMessageVia>(), _parent->Get<HistoryMessageReply>(), _parent->Get<HistoryMessageForwarded>());
}
int additionalWidth(
const HistoryMessageVia *via,
const HistoryMessageReply *reply,
const HistoryMessageForwarded *forwarded) const;
int additionalWidth() const;
QString mediaTypeString() const;
bool isSeparateRoundVideo() const;
@@ -770,9 +686,7 @@ public:
private:
int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const;
int additionalWidth() const {
return additionalWidth(_parent->Get<HistoryMessageVia>(), _parent->Get<HistoryMessageReply>());
}
int additionalWidth() const;
QString toString() const;
int16 _pixw = 1;
@@ -944,6 +858,7 @@ public:
void initDimensions() override;
int resizeGetHeight(int width) override;
void refreshParentId(not_null<HistoryItem*> realParent) override;
void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
@@ -970,9 +885,7 @@ public:
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
bool isDisplayed() const override {
return !_data->pendingTill && !_parent->Has<HistoryMessageLogEntryOriginal>();
}
bool isDisplayed() const override;
PhotoData *getPhoto() const override {
return _attach ? _attach->getPhoto() : nullptr;
}
@@ -1060,7 +973,7 @@ public:
void initDimensions() override;
int resizeGetHeight(int width) override;
void updateMessageId() override;
void refreshParentId(not_null<HistoryItem*> realParent) override;
void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
@@ -1148,7 +1061,7 @@ private:
int bottomInfoPadding() const;
not_null<GameData*> _data;
ClickHandlerPtr _openl;
std::shared_ptr<ReplyMarkupClickHandler> _openl;
std::unique_ptr<HistoryMedia> _attach;
int32 _titleLines, _descriptionLines;
@@ -1181,6 +1094,7 @@ public:
void initDimensions() override;
int resizeGetHeight(int width) override;
void refreshParentId(not_null<HistoryItem*> realParent) override;
MsgId getReceiptMsgId() const {
return _receiptMsgId;

View File

@@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "mainwindow.h"
#include "apiwrap.h"
#include "history/history_item_components.h"
#include "history/history_location_manager.h"
#include "history/history_service_layout.h"
#include "history/history_media_types.h"
@@ -32,6 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "boxes/share_box.h"
#include "boxes/confirm_box.h"
#include "ui/toast/toast.h"
#include "ui/text_options.h"
#include "messenger.h"
#include "styles/style_dialogs.h"
#include "styles/style_widgets.h"
@@ -46,9 +48,95 @@ namespace {
constexpr auto kPinnedMessageTextLimit = 16;
inline void initTextOptions() {
_historySrvOptions.dir = _textNameOptions.dir = _textDlgOptions.dir = cLangDir();
_textDlgOptions.maxw = st::columnMaximalWidthLeft * 2;
class KeyboardStyle : public ReplyKeyboard::Style {
public:
using ReplyKeyboard::Style::Style;
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;
};
void KeyboardStyle::startPaint(Painter &p) const {
p.setPen(st::msgServiceFg);
}
const style::TextStyle &KeyboardStyle::textStyle() const {
return st::serviceTextStyle;
}
void KeyboardStyle::repaint(not_null<const HistoryItem*> item) const {
Auth().data().requestItemRepaint(item);
}
int KeyboardStyle::buttonRadius() const {
return st::dateRadius;
}
void KeyboardStyle::paintButtonBg(
Painter &p,
const QRect &rect,
float64 howMuchOver) const {
App::roundRect(p, rect, st::msgServiceBg, StickerCorners);
if (howMuchOver > 0) {
auto o = p.opacity();
p.setOpacity(o * howMuchOver);
App::roundRect(p, rect, st::msgBotKbOverBgAdd, BotKbOverCorners);
p.setOpacity(o);
}
}
void KeyboardStyle::paintButtonIcon(
Painter &p,
const QRect &rect,
int outerWidth,
HistoryMessageMarkupButton::Type type) const {
using Button = HistoryMessageMarkupButton;
auto getIcon = [](Button::Type type) -> const style::icon* {
switch (type) {
case Button::Type::Url: return &st::msgBotKbUrlIcon;
case Button::Type::SwitchInlineSame:
case Button::Type::SwitchInline: return &st::msgBotKbSwitchPmIcon;
}
return nullptr;
};
if (auto icon = getIcon(type)) {
icon->paint(p, rect.x() + rect.width() - icon->width() - st::msgBotKbIconPadding, rect.y() + st::msgBotKbIconPadding, outerWidth);
}
}
void KeyboardStyle::paintButtonLoading(Painter &p, const QRect &rect) const {
auto icon = &st::historySendingInvertedIcon;
icon->paint(p, rect.x() + rect.width() - icon->width() - st::msgBotKbIconPadding, rect.y() + rect.height() - icon->height() - st::msgBotKbIconPadding, rect.x() * 2 + rect.width());
}
int KeyboardStyle::minButtonWidth(
HistoryMessageMarkupButton::Type type) const {
using Button = HistoryMessageMarkupButton;
int result = 2 * buttonPadding(), iconWidth = 0;
switch (type) {
case Button::Type::Url: iconWidth = st::msgBotKbUrlIcon.width(); break;
case Button::Type::SwitchInlineSame:
case Button::Type::SwitchInline: iconWidth = st::msgBotKbSwitchPmIcon.width(); break;
case Button::Type::Callback:
case Button::Type::Game: iconWidth = st::historySendingInvertedIcon.width(); break;
}
if (iconWidth > 0) {
result = std::max(result, 2 * iconWidth + 4 * int(st::msgBotKbIconPadding));
}
return result;
}
QString AdminBadgeText() {
@@ -190,12 +278,13 @@ void FastShareMessage(not_null<HistoryItem*> item) {
MessageIdsList msgIds;
base::flat_set<mtpRequestId> requests;
};
const auto data = MakeShared<ShareData>(item->history()->peer, [&] {
const auto data = std::make_shared<ShareData>(item->history()->peer, [&] {
if (const auto group = item->getFullGroup()) {
return Auth().data().groupToIds(group);
}
return MessageIdsList(1, item->fullId());
}());
const auto isGroup = (item->getFullGroup() != nullptr);
const auto isGame = item->getMessageBot()
&& item->getMedia()
&& (item->getMedia()->type() == MediaTypeGame);
@@ -222,7 +311,7 @@ void FastShareMessage(not_null<HistoryItem*> item) {
}
}
};
auto submitCallback = [data](const QVector<PeerData*> &result) {
auto submitCallback = [data, isGroup](const QVector<PeerData*> &result) {
if (!data->requests.empty()) {
return; // Share clicked already.
}
@@ -263,8 +352,11 @@ void FastShareMessage(not_null<HistoryItem*> item) {
}
};
auto sendFlags = MTPmessages_ForwardMessages::Flag::f_with_my_score
| MTPmessages_ForwardMessages::Flag::f_grouped;
const auto sendFlags = MTPmessages_ForwardMessages::Flag(0)
| MTPmessages_ForwardMessages::Flag::f_with_my_score
| (isGroup
? MTPmessages_ForwardMessages::Flag::f_grouped
: MTPmessages_ForwardMessages::Flag(0));
auto msgIds = QVector<MTPint>();
msgIds.reserve(data->msgIds.size());
for (const auto fullId : data->msgIds) {
@@ -313,11 +405,8 @@ void FastShareMessage(not_null<HistoryItem*> item) {
std::move(filterCallback)));
}
void HistoryInitMessages() {
initTextOptions();
}
base::lambda<void(ChannelData*, MsgId)> HistoryDependentItemCallback(const FullMsgId &msgId) {
base::lambda<void(ChannelData*, MsgId)> HistoryDependentItemCallback(
const FullMsgId &msgId) {
return [dependent = msgId](ChannelData *channel, MsgId msgId) {
if (auto item = App::histItemById(dependent)) {
item->updateDependencyItem();
@@ -359,293 +448,26 @@ QString GetErrorTextForForward(
return QString();
}
void HistoryMessageVia::create(UserId userId) {
_bot = App::user(peerFromUser(userId));
_maxWidth = st::msgServiceNameFont->width(lng_inline_bot_via(lt_inline_bot, '@' + _bot->username));
_lnk = MakeShared<LambdaClickHandler>([bot = _bot] {
App::insertBotCommand('@' + bot->username);
});
}
struct HistoryMessage::CreateConfig {
MsgId replyTo = 0;
UserId viaBotId = 0;
int viewsCount = -1;
QString author;
PeerId senderOriginal = 0;
MsgId originalId = 0;
PeerId savedFromPeer = 0;
MsgId savedFromMsgId = 0;
QString authorOriginal;
QDateTime originalDate;
QDateTime editDate;
MessageGroupId groupId = MessageGroupId::None;
void HistoryMessageVia::resize(int32 availw) const {
if (availw < 0) {
_text = QString();
_width = 0;
} else {
_text = lng_inline_bot_via(lt_inline_bot, '@' + _bot->username);
if (availw < _maxWidth) {
_text = st::msgServiceNameFont->elided(_text, availw);
_width = st::msgServiceNameFont->width(_text);
} else if (_width < _maxWidth) {
_width = _maxWidth;
}
}
}
// For messages created from MTP structs.
const MTPReplyMarkup *mtpMarkup = nullptr;
void HistoryMessageSigned::create(const QString &author, const QString &date) {
auto time = qsl(", ") + date;
auto name = author;
auto timew = st::msgDateFont->width(time);
auto namew = st::msgDateFont->width(name);
if (timew + namew > st::maxSignatureSize) {
name = st::msgDateFont->elided(author, st::maxSignatureSize - timew);
}
_author = author;
_signature.setText(st::msgDateTextStyle, name + time, _textNameOptions);
}
int HistoryMessageSigned::maxWidth() const {
return _signature.maxWidth();
}
void HistoryMessageEdited::refresh(const QString &date, bool displayed) {
const auto prefix = displayed ? (lang(lng_edited) + ' ') : QString();
text.setText(st::msgDateTextStyle, prefix + date, _textNameOptions);
}
int HistoryMessageEdited::maxWidth() const {
return text.maxWidth();
}
void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
QString text;
auto fromChannel = (_originalSender->isChannel() && !_originalSender->isMegagroup());
if (!_originalAuthor.isEmpty()) {
text = lng_forwarded_signed(lt_channel, App::peerName(_originalSender), lt_user, _originalAuthor);
} else {
text = App::peerName(_originalSender);
}
if (via) {
if (fromChannel) {
text = lng_forwarded_channel_via(lt_channel, textcmdLink(1, text), lt_inline_bot, textcmdLink(2, '@' + via->_bot->username));
} else {
text = lng_forwarded_via(lt_user, textcmdLink(1, text), lt_inline_bot, textcmdLink(2, '@' + via->_bot->username));
}
} else {
if (fromChannel) {
text = lng_forwarded_channel(lt_channel, textcmdLink(1, text));
} else {
text = lng_forwarded(lt_user, textcmdLink(1, text));
}
}
TextParseOptions opts = { TextParseRichText, 0, 0, Qt::LayoutDirectionAuto };
_text.setText(st::fwdTextStyle, text, opts);
_text.setLink(1, fromChannel ? goToMessageClickHandler(_originalSender, _originalId) : _originalSender->openLink());
if (via) {
_text.setLink(2, via->_lnk);
}
}
bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) {
if (!force) {
if (replyToMsg || !replyToMsgId) {
return true;
}
}
if (!replyToMsg) {
replyToMsg = App::histItemById(holder->channelId(), replyToMsgId);
if (replyToMsg) {
if (replyToMsg->isEmpty()) {
// Really it is deleted.
replyToMsg = nullptr;
force = true;
} else {
App::historyRegDependency(holder, replyToMsg);
}
}
}
if (replyToMsg) {
replyToText.setText(st::messageTextStyle, TextUtilities::Clean(replyToMsg->inReplyText()), _textDlgOptions);
updateName();
replyToLnk = goToMessageClickHandler(replyToMsg);
if (!replyToMsg->Has<HistoryMessageForwarded>()) {
if (auto bot = replyToMsg->viaBot()) {
_replyToVia = std::make_unique<HistoryMessageVia>();
_replyToVia->create(peerToUser(bot->id));
}
}
} else if (force) {
replyToMsgId = 0;
}
if (force) {
holder->setPendingInitDimensions();
}
return (replyToMsg || !replyToMsgId);
}
void HistoryMessageReply::clearData(HistoryMessage *holder) {
_replyToVia = nullptr;
if (replyToMsg) {
App::historyUnregDependency(holder, replyToMsg);
replyToMsg = nullptr;
}
replyToMsgId = 0;
}
bool HistoryMessageReply::isNameUpdated() const {
if (replyToMsg && replyToMsg->author()->nameVersion > replyToVersion) {
updateName();
return true;
}
return false;
}
void HistoryMessageReply::updateName() const {
if (replyToMsg) {
QString name = (_replyToVia && replyToMsg->author()->isUser()) ? replyToMsg->author()->asUser()->firstName : App::peerName(replyToMsg->author());
replyToName.setText(st::fwdTextStyle, name, _textNameOptions);
replyToVersion = replyToMsg->author()->nameVersion;
bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false;
int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
int32 w = replyToName.maxWidth();
if (_replyToVia) {
w += st::msgServiceFont->spacew + _replyToVia->_maxWidth;
}
_maxReplyWidth = previewSkip + qMax(w, qMin(replyToText.maxWidth(), int32(st::maxSignatureSize)));
} else {
_maxReplyWidth = st::msgDateFont->width(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message));
}
_maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + _maxReplyWidth + st::msgReplyPadding.right();
}
void HistoryMessageReply::resize(int width) const {
if (_replyToVia) {
bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false;
int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
_replyToVia->resize(width - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew);
}
}
void HistoryMessageReply::itemRemoved(HistoryMessage *holder, HistoryItem *removed) {
if (replyToMsg == removed) {
clearData(holder);
holder->setPendingInitDimensions();
}
}
void HistoryMessageReply::paint(Painter &p, const HistoryItem *holder, int x, int y, int w, PaintFlags flags) const {
bool selected = (flags & PaintFlag::Selected), outbg = holder->hasOutLayout();
style::color bar = st::msgImgReplyBarColor;
if (flags & PaintFlag::InBubble) {
bar = (flags & PaintFlag::Selected) ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor);
}
QRect rbar(rtlrect(x + st::msgReplyBarPos.x(), y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), w + 2 * x));
p.fillRect(rbar, bar);
if (w > st::msgReplyBarSkip) {
if (replyToMsg) {
auto hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false;
if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) {
hasPreview = false;
}
auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
if (hasPreview) {
ImagePtr replyPreview = replyToMsg->getMedia()->replyPreview();
if (!replyPreview->isNull()) {
auto to = rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x);
auto previewWidth = replyPreview->width() / cIntRetinaFactor();
auto previewHeight = replyPreview->height() / cIntRetinaFactor();
auto preview = replyPreview->pixSingle(previewWidth, previewHeight, to.width(), to.height(), ImageRoundRadius::Small, ImageRoundCorner::All, selected ? &st::msgStickerOverlay : nullptr);
p.drawPixmap(to.x(), to.y(), preview);
}
}
if (w > st::msgReplyBarSkip + previewSkip) {
if (flags & PaintFlag::InBubble) {
p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg));
} else {
p.setPen(st::msgImgReplyBarColor);
}
replyToName.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x);
if (_replyToVia && w > st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew) {
p.setFont(st::msgServiceFont);
p.drawText(x + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, _replyToVia->_text);
}
auto replyToAsMsg = replyToMsg->toHistoryMessage();
if (!(flags & PaintFlag::InBubble)) {
} else if ((replyToAsMsg && replyToAsMsg->emptyText()) || replyToMsg->serviceMsg()) {
p.setPen(outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg));
} else {
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
}
replyToText.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top() + st::msgServiceNameFont->height, w - st::msgReplyBarSkip - previewSkip, w + 2 * x);
}
} else {
p.setFont(st::msgDateFont);
auto &date = outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg);
p.setPen((flags & PaintFlag::InBubble) ? date : st::msgDateImgFg);
p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message), w - st::msgReplyBarSkip));
}
}
}
void HistoryMessage::KeyboardStyle::startPaint(Painter &p) const {
p.setPen(st::msgServiceFg);
}
const style::TextStyle &HistoryMessage::KeyboardStyle::textStyle() const {
return st::serviceTextStyle;
}
void HistoryMessage::KeyboardStyle::repaint(not_null<const HistoryItem*> item) const {
Auth().data().requestItemRepaint(item);
}
int HistoryMessage::KeyboardStyle::buttonRadius() const {
return st::dateRadius;
}
void HistoryMessage::KeyboardStyle::paintButtonBg(Painter &p, const QRect &rect, float64 howMuchOver) const {
App::roundRect(p, rect, st::msgServiceBg, StickerCorners);
if (howMuchOver > 0) {
auto o = p.opacity();
p.setOpacity(o * howMuchOver);
App::roundRect(p, rect, st::msgBotKbOverBgAdd, BotKbOverCorners);
p.setOpacity(o);
}
}
void HistoryMessage::KeyboardStyle::paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageReplyMarkup::Button::Type type) const {
using Button = HistoryMessageReplyMarkup::Button;
auto getIcon = [](Button::Type type) -> const style::icon* {
switch (type) {
case Button::Type::Url: return &st::msgBotKbUrlIcon;
case Button::Type::SwitchInlineSame:
case Button::Type::SwitchInline: return &st::msgBotKbSwitchPmIcon;
}
return nullptr;
};
if (auto icon = getIcon(type)) {
icon->paint(p, rect.x() + rect.width() - icon->width() - st::msgBotKbIconPadding, rect.y() + st::msgBotKbIconPadding, outerWidth);
}
}
void HistoryMessage::KeyboardStyle::paintButtonLoading(Painter &p, const QRect &rect) const {
auto icon = &st::historySendingInvertedIcon;
icon->paint(p, rect.x() + rect.width() - icon->width() - st::msgBotKbIconPadding, rect.y() + rect.height() - icon->height() - st::msgBotKbIconPadding, rect.x() * 2 + rect.width());
}
int HistoryMessage::KeyboardStyle::minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const {
using Button = HistoryMessageReplyMarkup::Button;
int result = 2 * buttonPadding(), iconWidth = 0;
switch (type) {
case Button::Type::Url: iconWidth = st::msgBotKbUrlIcon.width(); break;
case Button::Type::SwitchInlineSame:
case Button::Type::SwitchInline: iconWidth = st::msgBotKbSwitchPmIcon.width(); break;
case Button::Type::Callback:
case Button::Type::Game: iconWidth = st::historySendingInvertedIcon.width(); break;
}
if (iconWidth > 0) {
result = std::max(result, 2 * iconWidth + 4 * int(st::msgBotKbIconPadding));
}
return result;
}
// For messages created from existing messages (forwarded).
const HistoryMessageReplyMarkup *inlineMarkup = nullptr;
};
HistoryMessage::HistoryMessage(
not_null<History*> history,
@@ -918,12 +740,26 @@ void HistoryMessage::updateMediaInBubbleState() {
_media->setInBubbleState(computeState());
}
int HistoryMessage::viewsCount() const {
if (const auto views = Get<HistoryMessageViews>()) {
return views->_views;
}
return HistoryItem::viewsCount();
}
not_null<PeerData*> HistoryMessage::displayFrom() const {
return history()->peer->isSelf()
? senderOriginal()
: author();
}
bool HistoryMessage::updateDependencyItem() {
if (const auto reply = Get<HistoryMessageReply>()) {
return reply->updateData(this, true);
}
return true;
}
void HistoryMessage::updateAdminBadgeState() {
auto hasAdminBadge = [&] {
if (auto channel = history()->peer->asChannel()) {
@@ -1014,7 +850,7 @@ bool HistoryMessage::displayFastShare() const {
bool HistoryMessage::displayGoToOriginal() const {
if (_history->peer->isSelf()) {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_savedFromPeer && forwarded->_savedFromMsgId;
return forwarded->savedFromPeer && forwarded->savedFromMsgId;
}
}
return false;
@@ -1080,13 +916,16 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
if (const auto edited = Get<HistoryMessageEdited>()) {
edited->date = config.editDate;
}
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->author = config.author;
}
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
forwarded->_originalDate = config.originalDate;
forwarded->_originalSender = App::peer(config.senderOriginal);
forwarded->_originalId = config.originalId;
forwarded->_originalAuthor = config.authorOriginal;
forwarded->_savedFromPeer = App::peerLoaded(config.savedFromPeer);
forwarded->_savedFromMsgId = config.savedFromMsgId;
forwarded->originalDate = config.originalDate;
forwarded->originalSender = App::peer(config.senderOriginal);
forwarded->originalId = config.originalId;
forwarded->originalAuthor = config.authorOriginal;
forwarded->savedFromPeer = App::peerLoaded(config.savedFromPeer);
forwarded->savedFromMsgId = config.savedFromMsgId;
}
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
if (config.mtpMarkup) {
@@ -1220,7 +1059,7 @@ void HistoryMessage::replaceBuyWithReceiptInMarkup() {
if (auto markup = inlineReplyMarkup()) {
for (auto &row : markup->rows) {
for (auto &button : row) {
if (button.type == HistoryMessageReplyMarkup::Button::Type::Buy) {
if (button.type == HistoryMessageMarkupButton::Type::Buy) {
button.text = lang(lng_payments_receipt_button);
}
}
@@ -1310,7 +1149,7 @@ void HistoryMessage::initDimensions() {
+ displayFrom()->nameText.maxWidth()
+ st::msgPadding.right();
if (via && !forwarded) {
namew += st::msgServiceFont->spacew + via->_maxWidth;
namew += st::msgServiceFont->spacew + via->maxWidth;
}
if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
auto badgeWidth = st::msgServiceFont->width(
@@ -1319,19 +1158,19 @@ void HistoryMessage::initDimensions() {
}
accumulate_max(_maxw, namew);
} else if (via && !forwarded) {
accumulate_max(_maxw, st::msgPadding.left() + via->_maxWidth + st::msgPadding.right());
accumulate_max(_maxw, st::msgPadding.left() + via->maxWidth + st::msgPadding.right());
}
if (forwarded) {
auto namew = st::msgPadding.left() + forwarded->_text.maxWidth() + st::msgPadding.right();
auto namew = st::msgPadding.left() + forwarded->text.maxWidth() + st::msgPadding.right();
if (via) {
namew += st::msgServiceFont->spacew + via->_maxWidth;
namew += st::msgServiceFont->spacew + via->maxWidth;
}
accumulate_max(_maxw, namew);
}
if (reply) {
auto replyw = st::msgPadding.left() + reply->_maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right();
if (reply->_replyToVia) {
replyw += st::msgServiceFont->spacew + reply->_replyToVia->_maxWidth;
auto replyw = st::msgPadding.left() + reply->maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right();
if (reply->replyToVia) {
replyw += st::msgServiceFont->spacew + reply->replyToVia->maxWidth;
}
accumulate_max(_maxw, replyw);
}
@@ -1482,11 +1321,11 @@ void HistoryMessage::refreshEditedBadge() {
if (edited) {
edited->refresh(dateText, !editDate.isNull());
}
if (auto msgsigned = Get<HistoryMessageSigned>()) {
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
const auto text = (!edited || editDate.isNull())
? dateText
: edited->text.originalText();
msgsigned->create(msgsigned->_author, text);
msgsigned->refresh(text);
}
initTime();
}
@@ -1500,7 +1339,7 @@ bool HistoryMessage::displayForwardedFrom() const {
|| !_media
|| !_media->isDisplayed()
|| !_media->hideForwardedFrom()
|| forwarded->_originalSender->isChannel();
|| forwarded->originalSender->isChannel();
}
return false;
}
@@ -1528,9 +1367,9 @@ void HistoryMessage::updateMedia(const MTPMessageMedia *media) {
setPendingInitDimensions();
}
void HistoryMessage::addToUnreadMentions(AddToUnreadMentionsMethod method) {
if (indexInUnreadMentions() && mentionsMe() && isMediaUnread()) {
if (history()->addToUnreadMentions(id, method)) {
void HistoryMessage::addToUnreadMentions(UnreadMentionType type) {
if (IsServerMsgId(id) && mentionsMe() && isMediaUnread()) {
if (history()->addToUnreadMentions(id, type)) {
Notify::peerUpdatedDelayed(
history()->peer,
Notify::PeerUpdate::Flag::UnreadMentionsChanged);
@@ -1600,7 +1439,7 @@ TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
}
if (auto forwarded = Get<HistoryMessageForwarded>()) {
if (selection == FullSelection) {
auto fwdinfo = forwarded->_text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
auto fwdinfo = forwarded->text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
auto wrapped = TextWithEntities();
wrapped.text.reserve(fwdinfo.text.size() + 4 + result.text.size());
wrapped.entities.reserve(fwdinfo.entities.size() + result.entities.size());
@@ -1654,9 +1493,18 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
} else {
auto mediaOnBottom = (_media && _media->isDisplayed() && _media->isBubbleBottom()) || Has<HistoryMessageLogEntryOriginal>();
if (mediaOnBottom) {
_text.setMarkedText(st::messageTextStyle, textWithEntities, itemTextOptions(this));
_text.setMarkedText(
st::messageTextStyle,
textWithEntities,
Ui::ItemTextOptions(this));
} else {
_text.setMarkedText(st::messageTextStyle, { textWithEntities.text + skipBlock(), textWithEntities.entities }, itemTextOptions(this));
_text.setMarkedText(
st::messageTextStyle,
{
textWithEntities.text + skipBlock(),
textWithEntities.entities
},
Ui::ItemTextOptions(this));
}
_textWidth = -1;
_textHeight = 0;
@@ -1664,7 +1512,10 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
}
void HistoryMessage::setEmptyText() {
_text.setMarkedText(st::messageTextStyle, { QString(), EntitiesInText() }, itemTextOptions(this));
_text.setMarkedText(
st::messageTextStyle,
{ QString(), EntitiesInText() },
Ui::ItemTextOptions(this));
_textWidth = -1;
_textHeight = 0;
@@ -1791,7 +1642,7 @@ void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, int32 width
dateX += HistoryMessage::timeLeft();
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->_signature.drawElided(p, dateX, dateY, _timeWidth);
msgsigned->signature.drawElided(p, dateX, dateY, _timeWidth);
} else if (const auto edited = displayedEditBadge()) {
edited->text.drawElided(p, dateX, dateY, _timeWidth);
} else {
@@ -2058,8 +1909,8 @@ void HistoryMessage::paintFromName(Painter &p, QRect &trect, bool selected) cons
if (via && !forwarded && availableWidth > 0) {
auto outbg = hasOutLayout();
p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg));
p.drawText(availableLeft, trect.top() + st::msgServiceFont->ascent, via->_text);
auto skipWidth = via->_width + st::msgServiceFont->spacew;
p.drawText(availableLeft, trect.top() + st::msgServiceFont->ascent, via->text);
auto skipWidth = via->width + st::msgServiceFont->spacew;
availableLeft += skipWidth;
availableWidth -= skipWidth;
}
@@ -2084,12 +1935,12 @@ void HistoryMessage::paintForwardedInfo(Painter &p, QRect &trect, bool selected)
p.setFont(serviceFont);
auto forwarded = Get<HistoryMessageForwarded>();
auto breakEverywhere = (forwarded->_text.countHeight(trect.width()) > 2 * serviceFont->height);
auto breakEverywhere = (forwarded->text.countHeight(trect.width()) > 2 * serviceFont->height);
p.setTextPalette(selected ? (outbg ? st::outFwdTextPaletteSelected : st::inFwdTextPaletteSelected) : (outbg ? st::outFwdTextPalette : st::inFwdTextPalette));
forwarded->_text.drawElided(p, trect.x(), trect.y(), trect.width(), 2, style::al_left, 0, -1, 0, breakEverywhere);
forwarded->text.drawElided(p, trect.x(), trect.y(), trect.width(), 2, style::al_left, 0, -1, 0, breakEverywhere);
p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outTextPalette : st::inTextPalette));
trect.setY(trect.y() + (((forwarded->_text.maxWidth() > trect.width()) ? 2 : 1) * serviceFont->height));
trect.setY(trect.y() + (((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * serviceFont->height));
}
}
@@ -2112,7 +1963,7 @@ void HistoryMessage::paintViaBotIdInfo(Painter &p, QRect &trect, bool selected)
if (auto via = Get<HistoryMessageVia>()) {
p.setFont(st::msgServiceNameFont);
p.setPen(selected ? (hasOutLayout() ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (hasOutLayout() ? st::msgOutServiceFg : st::msgInServiceFg));
p.drawTextLeft(trect.left(), trect.top(), width(), via->_text);
p.drawTextLeft(trect.left(), trect.top(), width(), via->text);
trect.setY(trect.y() + st::msgServiceNameFont->height);
}
}
@@ -2232,7 +2083,7 @@ int HistoryMessage::performResizeGetHeight() {
}
if (displayForwardedFrom()) {
auto fwdheight = ((forwarded->_text.maxWidth() > (countGeometry().width() - st::msgPadding.left() - st::msgPadding.right())) ? 2 : 1) * st::semiboldFont->height;
auto fwdheight = ((forwarded->text.maxWidth() > (countGeometry().width() - st::msgPadding.left() - st::msgPadding.right())) ? 2 : 1) * st::semiboldFont->height;
_height += fwdheight;
}
@@ -2399,9 +2250,9 @@ ClickHandlerPtr HistoryMessage::rightActionLink() const {
if (!_rightActionLink) {
const auto itemId = fullId();
const auto forwarded = Get<HistoryMessageForwarded>();
const auto savedFromPeer = forwarded ? forwarded->_savedFromPeer : nullptr;
const auto savedFromMsgId = forwarded ? forwarded->_savedFromMsgId : 0;
_rightActionLink = MakeShared<LambdaClickHandler>([=] {
const auto savedFromPeer = forwarded ? forwarded->savedFromPeer : nullptr;
const auto savedFromMsgId = forwarded ? forwarded->savedFromMsgId : 0;
_rightActionLink = std::make_shared<LambdaClickHandler>([=] {
if (auto item = App::histItemById(itemId)) {
if (savedFromPeer && savedFromMsgId) {
App::wnd()->controller()->showPeerHistory(
@@ -2438,7 +2289,7 @@ void HistoryMessage::updatePressed(QPoint point) {
if (displayFromName()) trect.setTop(trect.top() + st::msgNameFont->height);
if (displayForwardedFrom()) {
auto forwarded = Get<HistoryMessageForwarded>();
auto fwdheight = ((forwarded->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
trect.setTop(trect.top() + fwdheight);
}
if (Get<HistoryMessageReply>()) {
@@ -2481,8 +2332,8 @@ bool HistoryMessage::getStateFromName(
}
auto forwarded = Get<HistoryMessageForwarded>();
auto via = Get<HistoryMessageVia>();
if (via && !forwarded && point.x() >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && point.x() < trect.left() + user->nameText.maxWidth() + st::msgServiceFont->spacew + via->_width) {
outResult->link = via->_lnk;
if (via && !forwarded && point.x() >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && point.x() < trect.left() + user->nameText.maxWidth() + st::msgServiceFont->spacew + via->width) {
outResult->link = via->link;
return true;
}
}
@@ -2498,14 +2349,14 @@ bool HistoryMessage::getStateForwardedInfo(
const HistoryStateRequest &request) const {
if (displayForwardedFrom()) {
auto forwarded = Get<HistoryMessageForwarded>();
auto fwdheight = ((forwarded->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
if (point.y() >= trect.top() && point.y() < trect.top() + fwdheight) {
auto breakEverywhere = (forwarded->_text.countHeight(trect.width()) > 2 * st::semiboldFont->height);
auto breakEverywhere = (forwarded->text.countHeight(trect.width()) > 2 * st::semiboldFont->height);
auto textRequest = request.forText();
if (breakEverywhere) {
textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere;
}
*outResult = HistoryTextState(this, forwarded->_text.getState(
*outResult = HistoryTextState(this, forwarded->text.getState(
point - trect.topLeft(),
trect.width(),
textRequest));
@@ -2546,8 +2397,8 @@ bool HistoryMessage::getStateViaBotIdInfo(
not_null<HistoryTextState*> outResult) const {
if (!displayFromName() && !Has<HistoryMessageForwarded>()) {
if (auto via = Get<HistoryMessageVia>()) {
if (QRect(trect.x(), trect.y(), via->_width, st::msgNameFont->height).contains(point)) {
outResult->link = via->_lnk;
if (QRect(trect.x(), trect.y(), via->width, st::msgNameFont->height).contains(point)) {
outResult->link = via->link;
return true;
}
trect.setTop(trect.top() + st::msgNameFont->height);

View File

@@ -20,15 +20,21 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
void HistoryInitMessages();
base::lambda<void(ChannelData*, MsgId)> HistoryDependentItemCallback(const FullMsgId &msgId);
#include "history/history_item.h"
struct HistoryMessageEdited;
base::lambda<void(ChannelData*, MsgId)> HistoryDependentItemCallback(
const FullMsgId &msgId);
MTPDmessage::Flags NewMessageFlags(not_null<PeerData*> peer);
QString GetErrorTextForForward(
not_null<PeerData*> peer,
const HistoryItemsList &items);
void FastShareMessage(not_null<HistoryItem*> item);
class HistoryMessage : public HistoryItem, private HistoryItemInstantiated<HistoryMessage> {
class HistoryMessage
: public HistoryItem
, private HistoryItemInstantiated<HistoryMessage> {
public:
static not_null<HistoryMessage*> create(
not_null<History*> history,
@@ -200,7 +206,7 @@ public:
setReplyMarkup(markup);
}
void addToUnreadMentions(AddToUnreadMentionsMethod method) override;
void addToUnreadMentions(UnreadMentionType type) override;
void eraseFromUnreadMentions() override;
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
@@ -218,21 +224,9 @@ public:
return _timeWidth;
}
int viewsCount() const override {
if (auto views = Get<HistoryMessageViews>()) {
return views->_views;
}
return HistoryItem::viewsCount();
}
int viewsCount() const override;
not_null<PeerData*> displayFrom() const;
bool updateDependencyItem() override {
if (auto reply = Get<HistoryMessageReply>()) {
return reply->updateData(this, true);
}
return true;
}
bool updateDependencyItem() override;
MsgId dependencyMsgId() const override {
return replyToId();
}
@@ -372,47 +366,10 @@ private:
mutable ClickHandlerPtr _rightActionLink;
mutable int32 _fromNameVersion = 0;
struct CreateConfig {
MsgId replyTo = 0;
UserId viaBotId = 0;
int viewsCount = -1;
QString author;
PeerId senderOriginal = 0;
MsgId originalId = 0;
PeerId savedFromPeer = 0;
MsgId savedFromMsgId = 0;
QString authorOriginal;
QDateTime originalDate;
QDateTime editDate;
MessageGroupId groupId = MessageGroupId::None;
// For messages created from MTP structs.
const MTPReplyMarkup *mtpMarkup = nullptr;
// For messages created from existing messages (forwarded).
const HistoryMessageReplyMarkup *inlineMarkup = nullptr;
};
struct CreateConfig;
void createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup);
void createComponents(const CreateConfig &config);
class KeyboardStyle : public ReplyKeyboard::Style {
public:
using ReplyKeyboard::Style::Style;
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;
};
void updateMediaInBubbleState();
void updateAdminBadgeState();

View File

@@ -26,9 +26,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_service_layout.h"
#include "history/history_media_types.h"
#include "history/history_message.h"
#include "history/history_item_components.h"
#include "auth_session.h"
#include "window/notifications_manager.h"
#include "storage/storage_shared_media.h"
#include "ui/text_options.h"
namespace {
@@ -36,13 +38,6 @@ constexpr auto kPinnedMessageTextLimit = 16;
} // namespace
TextParseOptions _historySrvOptions = {
TextParseLinks | TextParseMentions | TextParseHashtags/* | TextParseMultiline*/ | TextParseRichText, // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // lang-dependent
};
void HistoryService::setMessageByAction(const MTPmessageAction &action) {
auto prepareChatAddUserText = [this](const MTPDmessageActionChatAddUser &action) {
auto result = PreparedText {};
@@ -357,9 +352,15 @@ HistoryService::PreparedText HistoryService::prepareGameScoreText() {
auto computeGameTitle = [gamescore, &result]() -> QString {
if (gamescore && gamescore->msg) {
if (auto media = gamescore->msg->getMedia()) {
if (const auto media = gamescore->msg->getMedia()) {
if (media->type() == MediaTypeGame) {
result.links.push_back(MakeShared<ReplyMarkupClickHandler>(gamescore->msg, 0, 0));
const auto row = 0;
const auto column = 0;
result.links.push_back(
std::make_shared<ReplyMarkupClickHandler>(
row,
column,
gamescore->msg->fullId()));
auto titleText = static_cast<HistoryGame*>(media)->game()->title;
return textcmdLink(result.links.size(), titleText);
}
@@ -371,21 +372,37 @@ HistoryService::PreparedText HistoryService::prepareGameScoreText() {
return QString();
};
auto scoreNumber = gamescore ? gamescore->score : 0;
const auto scoreNumber = gamescore ? gamescore->score : 0;
if (_from->isSelf()) {
auto gameTitle = computeGameTitle();
if (gameTitle.isEmpty()) {
result.text = lng_action_game_you_scored_no_game(lt_count, scoreNumber);
result.text = lng_action_game_you_scored_no_game(
lt_count,
scoreNumber);
} else {
result.text = lng_action_game_you_scored(lt_count, scoreNumber, lt_game, gameTitle);
result.text = lng_action_game_you_scored(
lt_count,
scoreNumber,
lt_game,
gameTitle);
}
} else {
result.links.push_back(fromLink());
auto gameTitle = computeGameTitle();
if (gameTitle.isEmpty()) {
result.text = lng_action_game_score_no_game(lt_count, scoreNumber, lt_from, fromLinkText());
result.text = lng_action_game_score_no_game(
lt_count,
scoreNumber,
lt_from,
fromLinkText());
} else {
result.text = lng_action_game_score(lt_count, scoreNumber, lt_from, fromLinkText(), lt_game, gameTitle);
result.text = lng_action_game_score(
lt_count,
scoreNumber,
lt_from,
fromLinkText(),
lt_game,
gameTitle);
}
}
return result;
@@ -472,7 +489,10 @@ QString HistoryService::inReplyText() const {
}
void HistoryService::setServiceText(const PreparedText &prepared) {
_text.setText(st::serviceTextStyle, prepared.text, _historySrvOptions);
_text.setText(
st::serviceTextStyle,
prepared.text,
Ui::ItemTextServiceOptions());
auto linkIndex = 0;
for_const (auto &link, prepared.links) {
// Link indices start with 1.

View File

@@ -182,5 +182,3 @@ private:
static PreparedText GenerateText(not_null<History*> history, not_null<UserData*> inviter);
};
extern TextParseOptions _historySrvOptions;

View File

@@ -37,53 +37,16 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "auth_session.h"
#include "lang/lang_keys.h"
#include "ui/special_buttons.h"
#include "ui/unread_badge.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/dropdown_menu.h"
#include "dialogs/dialogs_layout.h"
#include "window/window_controller.h"
#include "window/window_peer_menu.h"
#include "calls/calls_instance.h"
#include "data/data_peer_values.h"
#include "observer_peer.h"
#include "apiwrap.h"
class HistoryTopBarWidget::UnreadBadge : public Ui::RpWidget {
public:
using RpWidget::RpWidget;
void setText(const QString &text, bool active) {
_text = text;
_active = active;
update();
}
protected:
void paintEvent(QPaintEvent *e) override;
private:
QString _text;
bool _active = false;
};
void HistoryTopBarWidget::UnreadBadge::paintEvent(QPaintEvent *e) {
if (_text.isEmpty()) {
return;
}
Painter p(this);
Dialogs::Layout::UnreadBadgeStyle unreadSt;
unreadSt.muted = !_active;
auto unreadRight = width();
auto unreadTop = 0;
Dialogs::Layout::paintUnreadCount(
p,
_text,
unreadRight,
unreadTop,
unreadSt);
}
HistoryTopBarWidget::HistoryTopBarWidget(
QWidget *parent,
not_null<Window::Controller*> controller)
@@ -114,26 +77,26 @@ HistoryTopBarWidget::HistoryTopBarWidget(
rpl::combine(
_controller->historyPeer.value(),
_controller->searchInPeer.value())
| rpl::combine_previous(std::make_tuple(nullptr, nullptr))
| rpl::map([](
const std::tuple<PeerData*, PeerData*> &previous,
const std::tuple<PeerData*, PeerData*> &current) {
auto peer = std::get<0>(current);
auto searchPeer = std::get<1>(current);
auto peerChanged = (peer != std::get<0>(previous));
auto searchInPeer
= (peer != nullptr) && (peer == searchPeer);
return std::make_tuple(searchInPeer, peerChanged);
})
| rpl::start_with_next([this](
bool searchInHistoryPeer,
bool peerChanged) {
auto animated = peerChanged
? anim::type::instant
: anim::type::normal;
_search->setForceRippled(searchInHistoryPeer, animated);
}, lifetime());
_controller->searchInPeer.value()
) | rpl::combine_previous(
std::make_tuple(nullptr, nullptr)
) | rpl::map([](
const std::tuple<PeerData*, PeerData*> &previous,
const std::tuple<PeerData*, PeerData*> &current) {
auto peer = std::get<0>(current);
auto searchPeer = std::get<1>(current);
auto peerChanged = (peer != std::get<0>(previous));
auto searchInPeer
= (peer != nullptr) && (peer == searchPeer);
return std::make_tuple(searchInPeer, peerChanged);
}) | rpl::start_with_next([this](
bool searchInHistoryPeer,
bool peerChanged) {
auto animated = peerChanged
? anim::type::instant
: anim::type::normal;
_search->setForceRippled(searchInHistoryPeer, animated);
}, lifetime());
subscribe(Adaptive::Changed(), [this] { updateAdaptiveLayout(); });
if (Adaptive::OneColumn()) {
@@ -163,10 +126,10 @@ HistoryTopBarWidget::HistoryTopBarWidget(
rpl::combine(
Auth().data().thirdSectionInfoEnabledValue(),
Auth().data().tabbedReplacedWithInfoValue())
| rpl::start_with_next(
[this] { updateInfoToggleActive(); },
lifetime());
Auth().data().tabbedReplacedWithInfoValue()
) | rpl::start_with_next(
[this] { updateInfoToggleActive(); },
lifetime());
setCursor(style::cur_pointer);
updateControlsVisibility();
@@ -664,12 +627,12 @@ void HistoryTopBarWidget::updateOnlineDisplay() {
if (!_historyPeer) return;
QString text;
int32 t = unixtime();
const auto now = unixtime();
bool titlePeerTextOnline = false;
if (auto user = _historyPeer->asUser()) {
text = App::onlineText(user, t);
titlePeerTextOnline = App::onlineColorUse(user, t);
} else if (auto chat = _historyPeer->asChat()) {
if (const auto user = _historyPeer->asUser()) {
text = Data::OnlineText(user, now);
titlePeerTextOnline = Data::OnlineTextActive(user, now);
} else if (const auto chat = _historyPeer->asChat()) {
if (!chat->amIn()) {
text = lang(lng_chat_status_unaccessible);
} else if (chat->participants.empty()) {
@@ -684,7 +647,7 @@ void HistoryTopBarWidget::updateOnlineDisplay() {
auto online = 0;
auto onlyMe = true;
for (auto [user, v] : chat->participants) {
if (user->onlineTill > t) {
if (user->onlineTill > now) {
++online;
if (onlyMe && user != App::self()) onlyMe = false;
}
@@ -707,7 +670,7 @@ void HistoryTopBarWidget::updateOnlineDisplay() {
auto online = 0;
bool onlyMe = true;
for (auto &participant : std::as_const(channel->mgInfo->lastParticipants)) {
if (participant->onlineTill > t) {
if (participant->onlineTill > now) {
++online;
if (onlyMe && participant != App::self()) {
onlyMe = false;
@@ -743,11 +706,10 @@ void HistoryTopBarWidget::updateOnlineDisplayTimer() {
if (!_historyPeer) return;
const auto now = unixtime();
auto minIn = TimeId(86400);
auto minTimeout = TimeMs(86400);
const auto handleUser = [&](not_null<UserData*> user) {
auto hisMinIn = App::onlineWillChangeIn(user, now);
Assert(hisMinIn >= 0 && hisMinIn <= 86400);
accumulate_min(minIn, hisMinIn);
auto hisTimeout = Data::OnlineChangeTimeout(user, now);
accumulate_min(minTimeout, hisTimeout);
};
if (const auto user = _historyPeer->asUser()) {
handleUser(user);
@@ -757,7 +719,7 @@ void HistoryTopBarWidget::updateOnlineDisplayTimer() {
}
} else if (_historyPeer->isChannel()) {
}
updateOnlineDisplayIn(minIn * 1000);
updateOnlineDisplayIn(minTimeout);
}
void HistoryTopBarWidget::updateOnlineDisplayIn(TimeMs timeout) {

Some files were not shown because too many files have changed in this diff Show More