Compare commits

...

105 Commits

Author SHA1 Message Date
John Preston
94cf307ae0 Alpha version 1.2.7.
- Use fast reply button in group chats.
- Select a message you want to reply to
by pressing Ctrl+Up and Ctrl+Down.
2017-12-31 17:55:09 +03:00
John Preston
2cc1fde5e4 Remove thumb glitch when uploading files. 2017-12-31 15:21:08 +03:00
John Preston
6796ac688a Apply web page media updates. 2017-12-31 12:55:08 +03:00
John Preston
9551cfaf9b Use more specific color keys for media overview parts.
File icons and radial animations should use derived keys instead of msgInBg.
Fixes #4246.
2017-12-31 01:28:25 +03:00
John Preston
8ef9ec0567 Fix glitches for transparent album preview parts. 2017-12-31 01:28:21 +03:00
John Preston
af552fb4c0 Replace base/task_queue with crl. 2017-12-31 00:28:38 +03:00
John Preston
ae7e5be5cd Add fast reply button in groups. 2017-12-30 21:54:15 +03:00
Matthew Tran
74b126f309 Add reply shortcut 2017-12-30 21:52:30 +03:00
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
John Preston
de16a66a4a Alpha version 1.2.2: Fix build for Xcode. 2017-12-16 21:09:37 +04:00
John Preston
b0f191515a Alpha version 1.2.2.
- Grouped photos and videos are displayed as albums.
2017-12-16 20:52:41 +04:00
John Preston
89ccaccb88 Display right edited badge in group with caption. 2017-12-16 20:50:43 +04:00
John Preston
1f070da202 Recount grouping after leader caption edit. 2017-12-16 20:50:43 +04:00
John Preston
963e969d2a Fix selected messages copy with grouping. 2017-12-16 20:50:43 +04:00
John Preston
4734700ac5 Improve opening history with one loaded message.
When we add just one last item, like we do while loading dialogs,
we want to remove a single added grouped media, otherwise it will
jump once we open the message history (first we show only that
media, then we load the rest of the group and show the group).

That way when we open the message history we show nothing until a
whole history part is loaded, it certainly will contain the group.
2017-12-16 20:50:43 +04:00
John Preston
d9da2edd7c Improve grouped media display. 2017-12-16 20:50:43 +04:00
John Preston
6d48ca850e Correct reply/forward/delete for grouped items. 2017-12-16 20:50:43 +04:00
John Preston
3e7ac7eb26 Use first media caption for group caption. 2017-12-16 20:50:43 +04:00
John Preston
520a644150 Fix drag by date of grouped media. 2017-12-16 20:50:43 +04:00
John Preston
3a56b7cabd Forward grouped items. Fast share grouped items. 2017-12-16 20:50:43 +04:00
John Preston
efa72578cd Fix grouped media display in MediaView. 2017-12-16 20:50:43 +04:00
John Preston
b6087ce7ce Select/forward/delete group of messages. 2017-12-16 20:50:42 +04:00
John Preston
537400d8b2 Enable distinct selecting of grouped media. 2017-12-16 20:50:42 +04:00
John Preston
4c9931ab02 Support grouped media rendering. 2017-12-16 20:50:42 +04:00
John Preston
0a4038d061 Fix build with TDESKTOP_DISABLE_CRASH_REPORTS.
Regression was introduced in 97c15865a5.

Fixes #4173.
2017-12-13 00:25:14 +04:00
Christoph
2d5188b968 Remove wrong alpha version
Fix #4171
2017-12-12 21:43:01 +04:00
John Preston
4bab7583ba Version 1.2.1.
- Bug fixes and other minor improvements.
2017-12-12 18:56:38 +04:00
John Preston
b2f29b674d Send audio files with correct attributes.
Regression was introduced in 8b69e6ab99.

Fixes #4163.
2017-12-12 18:56:38 +04:00
John Preston
574f4a73cb Add some checks to video sound stream decoding. 2017-12-12 18:56:37 +04:00
John Preston
05e3ddce0c Fix userpic removing.
Regression was introduced in 68009b6fba.

Fixes #4152.
2017-12-12 18:56:37 +04:00
John Preston
3c101b0a50 Remove limit on chats list width.
Fixes #4146.
2017-12-12 18:56:37 +04:00
John Preston
e998bd0b3f Parse command line natively on Windows.
Use CommandLineToArgvW() so that unicode arguments are preserved.
This will fix path arguments with unicode symbols in them.
2017-12-12 18:56:37 +04:00
John Preston
251176df47 Move relaunch / update logic to Core::Launcher.
Also pass "-workdir" argument through relaunch / update.

Fixes #4149.
2017-12-12 18:56:36 +04:00
John Preston
97c15865a5 Move some code around.
Move logs:SignalHandlers to core/crash_reports:CrashReports.
Move all pre-launch windows to core/crash_report_window module.
Move some global code to core/launcher:Launcher.
It should replace settings / platform_specific module in some way.
2017-12-12 16:47:32 +04:00
John Preston
9d4558de2b Fix build in Visual Studio 15.5.1.
Looks like compiler had some regressions when updating from 15.4.5.

Range-V3-VS2015 also needs to cherry-pick this commit:
https://github.com/ericniebler/range-v3/commit/9f990c48d0
See https://github.com/Microsoft/Range-V3-VS2015/issues/26
2017-12-12 12:25:54 +04:00
John Preston
38f7f48c17 Open links in AboutBox without confirmation.
Fixes #4148.
2017-12-12 12:25:54 +04:00
John Preston
9534121676 Fix issue number :/ Fixes #4150. 2017-12-11 15:19:13 +04:00
John Preston
10b76d921b Fix window scaling issue on macOS.
Fixes #4149.
2017-12-11 15:06:05 +04:00
300 changed files with 19671 additions and 10460 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";
@@ -860,12 +863,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_edited" = "edited";
"lng_edited_date" = "Edited: {date}";
"lng_admin_badge" = "admin";
"lng_fast_reply" = "Reply";
"lng_cancel_edit_post_sure" = "Cancel editing?";
"lng_cancel_edit_post_yes" = "Yes";
"lng_cancel_edit_post_no" = "No";
"lng_bot_share_location_unavailable" = "Sorry, the location sharing is currently unavailable in Telegram Desktop.";
"lng_bot_share_phone" = "Share Phone Number?";
"lng_bot_share_location_unavailable" = "Sorry, location sharing is currently unavailable in Telegram Desktop.";
"lng_bot_share_phone" = "Do you want to share your phone number with this bot?";
"lng_bot_share_phone_confirm" = "Share";
"lng_attach_failed" = "Failed";
@@ -900,7 +904,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_faved_stickers_add" = "Add to Favorites";
"lng_faved_stickers_remove" = "Remove from Favorites";
"lng_group_stickers" = "Group stickers";
"lng_group_stickers_description" = "You can choose sticker set which will be available for every member while in the group chat.";
"lng_group_stickers_description" = "You can choose a sticker set which will be available for every member while in the group chat.";
"lng_group_stickers_add" = "Choose sticker set";
"lng_switch_stickers" = "Stickers";
@@ -936,6 +940,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_stickers_group_from_featured" = "Choose from trending stickers";
"lng_in_dlg_photo" = "Photo";
"lng_in_dlg_album" = "Album";
"lng_in_dlg_video" = "Video";
"lng_in_dlg_audio_file" = "Audio file";
"lng_in_dlg_contact" = "Contact";
@@ -989,7 +994,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_open_this_link" = "Open this link?";
"lng_open_link" = "Open";
"lng_allow_bot_pass" = "Do you allow {bot_name} to pass your Telegram name and id to the web pages you open via this bot?";
"lng_allow_bot_pass" = "Allow {bot_name} to pass your Telegram name and ID to the web pages you open via this bot?";
"lng_allow_bot" = "Allow";
"lng_bot_start" = "Start";
@@ -998,7 +1003,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_bot_groups_not_found" = "No groups found";
"lng_bot_sure_invite" = "Add the bot to «{group}»?";
"lng_bot_already_in_group" = "The bot is already a member of the group.";
"lng_bot_choose_chat" = "Select a Chat";
"lng_bot_choose_chat" = "Select a chat";
"lng_bot_no_chats" = "You have no chats";
"lng_bot_chats_not_found" = "No chats found";
"lng_bot_sure_share_game" = "Share the game with {user}?";
@@ -1091,18 +1096,21 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_context_forward_selected" = "Forward Selected";
"lng_context_delete_selected" = "Delete Selected";
"lng_context_clear_selection" = "Clear Selection";
"lng_send_images_compress#one" = "Compress image";
"lng_send_images_compress#other" = "Compress images";
"lng_send_image_empty" = "Could not send an empty file: {name}";
"lng_send_image_too_large" = "Could not send a file, because it is larger than 1500 MB: {name}";
"lng_send_images_selected#one" = "{count} image selected";
"lng_send_images_selected#other" = "{count} images selected";
"lng_send_photos#one" = "Send {count} photo";
"lng_send_photos#other" = "Send {count} photos";
"lng_send_separate_photos" = "Send as separate photos";
"lng_send_separate_photos_videos" = "Send as separate media";
"lng_send_files_selected#one" = "{count} file selected";
"lng_send_files_selected#other" = "{count} files selected";
"lng_send_files#one" = "Send {count} file";
"lng_send_files#other" = "Send {count} files";
"lng_send_album" = "Send as an album";
"lng_send_photo" = "Send as a photo";
"lng_send_file" = "Send as a file";
"lng_forward_choose" = "Choose recipient...";
"lng_forward_cant" = "Sorry, no way to forward here :(";
@@ -1122,7 +1130,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_share_cant" = "Sorry, no way to share here :(";
"lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :(";
"lng_reply_cant_forward" = "Sorry, no way to reply to an old message in supergroup :( Do you wish to forward it and add your comment?";
"lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?";
"lng_share_title" = "Share to";
"lng_share_copy_link" = "Copy share link";
@@ -1131,7 +1139,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_share_game_link_copied" = "Game link copied to clipboard.";
"lng_share_done" = "Done!";
"lng_contact_phone" = "Phone number";
"lng_contact_phone" = "Phone Number";
"lng_enter_contact_data" = "New Contact";
"lng_edit_contact_title" = "Edit contact name";
"lng_edit_channel_title" = "Edit channel";
@@ -1145,8 +1153,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_add_contact" = "Create";
"lng_add_contact_button" = "New contact";
"lng_contacts_header" = "Contacts";
"lng_contact_not_joined" = "Unfortunately {name} did not join Telegram yet, but you can send your friend an invitation.\n\nWe will notify you about any of your contacts who is joining Telegram.";
"lng_try_other_contact" = "Try other";
"lng_contact_not_joined" = "Unfortunately {name} has not joined Telegram yet, but you can send them an invitation.\n\nWe will notify you about any of your contacts who join Telegram.";
"lng_try_other_contact" = "Try someone else";
"lng_create_group_link" = "Link";
"lng_create_group_invite_link" = "Invite link";
"lng_create_group_description" = "Description (optional)";
@@ -1233,7 +1241,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_confirm_phone_enter_code" = "Please enter the code.";
"lng_theme_editor_no_keys" = "No keys in the palette yet";
"lng_theme_editor_cant_change_theme" = "You can not apply new themes while you're editing the color palette. Please close the theme editor first.";
"lng_theme_editor_cant_change_theme" = "You can't apply a new theme while you're editing the colour palette. Please close the theme editor first.";
"lng_theme_editor_new_keys" = "Not in the palette yet";
"lng_theme_editor_background_image" = "Background image";
"lng_theme_editor_saved_to_jpg" = "Saved to JPEG, {size}";
@@ -1274,7 +1282,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_call_bar_hangup" = "End call";
"lng_call_box_title" = "Calls";
"lng_call_box_about" = "You didn't make any calls yet.";
"lng_call_box_about" = "You haven't made any Telegram calls yet.";
"lng_call_box_status_today" = "{time}";
"lng_call_box_status_yesterday" = "Yesterday at {time}";
"lng_call_box_status_date" = "{date} at {time}";
@@ -1295,7 +1303,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_player_message_yesterday" = "Yesterday at {time}";
"lng_player_message_date" = "{date} at {time}";
"lng_rights_edit_admin" = "Edit administrator";
"lng_rights_edit_admin" = "Manage permissions";
"lng_rights_edit_admin_header" = "What can this admin do?";
"lng_rights_about_add_admins_yes" = "This admin will be able to add new admins with the same (or more limited) permissions.";
"lng_rights_about_add_admins_no" = "This admin will not be able to add new admins.";
@@ -1399,9 +1407,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_admin_log_banned" = "banned {user}";
"lng_admin_log_restricted" = "changed restrictions for {user} {until}";
"lng_admin_log_promoted" = "changed privileges for {user}";
"lng_admin_log_changed_stickers_group" = "{from} changed group {sticker_set}";
"lng_admin_log_changed_stickers_group" = "{from} changed the group's {sticker_set}";
"lng_admin_log_changed_stickers_set" = "sticker set";
"lng_admin_log_removed_stickers_group" = "{from} removed group sticker set";
"lng_admin_log_removed_stickers_group" = "{from} removed the group's sticker set";
"lng_admin_log_user_with_username" = "{name} ({mention})";
"lng_admin_log_restricted_forever" = "indefinitely";
"lng_admin_log_restricted_until" = "until {date}";

View File

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

View File

@@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,2,0,0
PRODUCTVERSION 1,2,0,0
FILEVERSION 1,2,7,0
PRODUCTVERSION 1,2,7,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -52,10 +52,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "1.2.0.0"
VALUE "FileVersion", "1.2.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.2.0.0"
VALUE "ProductVersion", "1.2.7.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,2,0,0
PRODUCTVERSION 1,2,0,0
FILEVERSION 1,2,7,0
PRODUCTVERSION 1,2,7,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -43,10 +43,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "1.2.0.0"
VALUE "FileVersion", "1.2.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.2.0.0"
VALUE "ProductVersion", "1.2.7.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -22,7 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
bool _debug = false;
wstring updaterName, updaterDir, updateTo, exeName;
wstring updaterName, updaterDir, updateTo, exeName, customWorkingDir, customKeyFile;
bool equal(const wstring &a, const wstring &b) {
return !_wcsicmp(a.c_str(), b.c_str());
@@ -356,6 +356,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
args = CommandLineToArgvW(GetCommandLine(), &argsCount);
if (args) {
for (int i = 1; i < argsCount; ++i) {
writeLog(std::wstring(L"Argument: ") + args[i]);
if (equal(args[i], L"-update")) {
needupdate = true;
} else if (equal(args[i], L"-autostart")) {
@@ -368,17 +369,25 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
} else if (equal(args[i], L"-testmode")) {
testmode = true;
} else if (equal(args[i], L"-writeprotected") && ++i < argsCount) {
writeLog(std::wstring(L"Argument: ") + args[i]);
writeprotected = true;
updateTo = args[i];
for (int i = 0, l = updateTo.size(); i < l; ++i) {
if (updateTo[i] == L'/') {
updateTo[i] = L'\\';
for (int j = 0, l = updateTo.size(); j < l; ++j) {
if (updateTo[j] == L'/') {
updateTo[j] = L'\\';
}
}
} else if (equal(args[i], L"-workdir") && ++i < argsCount) {
writeLog(std::wstring(L"Argument: ") + args[i]);
customWorkingDir = args[i];
} else if (equal(args[i], L"-key") && ++i < argsCount) {
writeLog(std::wstring(L"Argument: ") + args[i]);
customKeyFile = args[i];
} else if (equal(args[i], L"-exename") && ++i < argsCount) {
writeLog(std::wstring(L"Argument: ") + args[i]);
exeName = args[i];
for (int i = 0, l = exeName.size(); i < l; ++i) {
if (exeName[i] == L'/' || exeName[i] == L'\\') {
for (int j = 0, l = exeName.size(); j < l; ++j) {
if (exeName[j] == L'/' || exeName[j] == L'\\') {
exeName = L"Telegram.exe";
break;
}
@@ -391,6 +400,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
if (needupdate) writeLog(L"Need to update!");
if (autostart) writeLog(L"From autostart!");
if (writeprotected) writeLog(L"Write Protected folder!");
if (!customWorkingDir.empty()) writeLog(L"Will pass custom working dir: " + customWorkingDir);
updaterName = args[0];
writeLog(L"Updater name is: " + updaterName);
@@ -428,6 +438,13 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
if (debug) targs += L" -debug";
if (startintray) targs += L" -startintray";
if (testmode) targs += L" -testmode";
if (!customWorkingDir.empty()) {
targs += L" -workdir \"" + customWorkingDir + L"\"";
}
if (!customKeyFile.empty()) {
targs += L" -key \"" + customKeyFile + L"\"";
}
writeLog(L"Result arguments: " + targs);
bool executed = false;
if (writeprotected) { // run un-elevated

View File

@@ -344,9 +344,16 @@ string CurrentExecutablePath(int argc, char *argv[]) {
}
int main(int argc, char *argv[]) {
bool needupdate = true, autostart = false, debug = false, tosettings = false, startintray = false, testmode = false;
bool needupdate = true;
bool autostart = false;
bool debug = false;
bool tosettings = false;
bool startintray = false;
bool testmode = false;
bool customWorkingDir = false;
char *key = 0, *crashreport = 0;
char *key = 0;
char *workdir = 0;
for (int i = 1; i < argc; ++i) {
if (equal(argv[i], "-noupdate")) {
needupdate = false;
@@ -360,12 +367,12 @@ int main(int argc, char *argv[]) {
testmode = true;
} else if (equal(argv[i], "-tosettings")) {
tosettings = true;
} else if (equal(argv[i], "-workdir_custom")) {
customWorkingDir = true;
} else if (equal(argv[i], "-key") && ++i < argc) {
key = argv[i];
} else if (equal(argv[i], "-workpath") && ++i < argc) {
workDir = argv[i];
} else if (equal(argv[i], "-crashreport") && ++i < argc) {
crashreport = argv[i];
workDir = workdir = argv[i];
} else if (equal(argv[i], "-exename") && ++i < argc) {
exeName = argv[i];
} else if (equal(argv[i], "-exepath") && ++i < argc) {
@@ -401,6 +408,8 @@ int main(int argc, char *argv[]) {
}
if (needupdate) {
if (workDir.empty()) { // old app launched, update prepared in tupdates/ready (not in tupdates/temp)
customWorkingDir = false;
writeLog("No workdir, trying to figure it out");
struct passwd *pw = getpwuid(getuid());
if (pw && pw->pw_dir && strlen(pw->pw_dir)) {
@@ -446,22 +455,30 @@ int main(int argc, char *argv[]) {
string fullBinaryPath = exePath + exeName;
strcpy(path, fullBinaryPath.c_str());
char *args[MaxArgsCount] = {0}, p_noupdate[] = "-noupdate", p_autostart[] = "-autostart", p_debug[] = "-debug", p_tosettings[] = "-tosettings", p_key[] = "-key", p_startintray[] = "-startintray", p_testmode[] = "-testmode";
char *args[MaxArgsCount] = { 0 };
char p_noupdate[] = "-noupdate";
char p_autostart[] = "-autostart";
char p_debug[] = "-debug";
char p_tosettings[] = "-tosettings";
char p_key[] = "-key";
char p_startintray[] = "-startintray";
char p_testmode[] = "-testmode";
char p_workdir[] = "-workdir";
int argIndex = 0;
args[argIndex++] = path;
if (crashreport) {
args[argIndex++] = crashreport;
} else {
args[argIndex++] = p_noupdate;
if (autostart) args[argIndex++] = p_autostart;
if (debug) args[argIndex++] = p_debug;
if (startintray) args[argIndex++] = p_startintray;
if (testmode) args[argIndex++] = p_testmode;
if (tosettings) args[argIndex++] = p_tosettings;
if (key) {
args[argIndex++] = p_key;
args[argIndex++] = key;
}
args[argIndex++] = p_noupdate;
if (autostart) args[argIndex++] = p_autostart;
if (debug) args[argIndex++] = p_debug;
if (startintray) args[argIndex++] = p_startintray;
if (testmode) args[argIndex++] = p_testmode;
if (tosettings) args[argIndex++] = p_tosettings;
if (key) {
args[argIndex++] = p_key;
args[argIndex++] = key;
}
if (customWorkingDir && workdir) {
args[argIndex++] = p_workdir;
args[argIndex++] = workdir;
}
pid_t pid = fork();
switch (pid) {

View File

@@ -23,7 +23,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
NSString *appName = @"Telegram.app";
NSString *appDir = nil;
NSString *workDir = nil;
NSString *crashReportArg = nil;
#ifdef _DEBUG
BOOL _debug = YES;
@@ -90,6 +89,7 @@ int main(int argc, const char * argv[]) {
openLog();
pid_t procId = 0;
BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO, testMode = NO;
BOOL customWorkingDir = NO;
NSString *key = nil;
for (int i = 0; i < argc; ++i) {
if ([@"-workpath" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
@@ -102,10 +102,6 @@ int main(int argc, const char * argv[]) {
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
procId = [[formatter numberFromString:[NSString stringWithUTF8String:argv[i]]] intValue];
}
} else if ([@"-crashreport" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
if (++i < argc) {
crashReportArg = [NSString stringWithUTF8String:argv[i]];
}
} else if ([@"-noupdate" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
update = NO;
} else if ([@"-tosettings" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
@@ -118,11 +114,16 @@ int main(int argc, const char * argv[]) {
startInTray = YES;
} else if ([@"-testmode" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
testMode = YES;
} else if ([@"-workdir_custom" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
customWorkingDir = YES;
} else if ([@"-key" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
if (++i < argc) key = [NSString stringWithUTF8String:argv[i]];
}
}
if (!workDir) workDir = appDir;
if (!workDir) {
workDir = appDir;
customWorkingDir = NO;
}
openLog();
NSMutableArray *argsArr = [[NSMutableArray alloc] initWithCapacity:argc];
for (int i = 0; i < argc; ++i) {
@@ -242,17 +243,19 @@ int main(int argc, const char * argv[]) {
}
NSString *appPath = [[NSArray arrayWithObjects:appDir, appRealName, nil] componentsJoinedByString:@""];
NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: crashReportArg ? crashReportArg : @"-noupdate", nil];
if (!crashReportArg) {
if (toSettings) [args addObject:@"-tosettings"];
if (_debug) [args addObject:@"-debug"];
if (startInTray) [args addObject:@"-startintray"];
if (testMode) [args addObject:@"-testmode"];
if (autoStart) [args addObject:@"-autostart"];
if (key) {
[args addObject:@"-key"];
[args addObject:key];
}
NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: @"-noupdate", nil];
if (toSettings) [args addObject:@"-tosettings"];
if (_debug) [args addObject:@"-debug"];
if (startInTray) [args addObject:@"-startintray"];
if (testMode) [args addObject:@"-testmode"];
if (autoStart) [args addObject:@"-autostart"];
if (key) {
[args addObject:@"-key"];
[args addObject:key];
}
if (customWorkingDir) {
[args addObject:@"-workdir"];
[args addObject:workDir];
}
writeLog([[NSArray arrayWithObjects:@"Running application '", appPath, @"' with args '", [args componentsJoinedByString:@"' '"], @"'..", nil] componentsJoinedByString:@""]);
NSError *error = nil;

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,71 +139,40 @@ ApiWrap::ApiWrap(not_null<AuthSession*> session)
, _messageDataResolveDelayed([this] { resolveMessageDatas(); })
, _webPagesTimer([this] { resolveWebPages(); })
, _draftsSaveTimer([this] { saveDraftsToCloud(); })
, _featuredSetsReadTimer([this] { readFeaturedSets(); }) {
, _featuredSetsReadTimer([this] { readFeaturedSets(); })
, _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout)) {
}
void ApiWrap::start() {
Window::Theme::Background()->start();
requestAppChangelogs();
void ApiWrap::requestChangelog(
const QString &sinceVersion,
base::lambda<void(const MTPUpdates &result)> callback) {
request(MTPhelp_GetAppChangelog(
MTP_string(sinceVersion)
)).done(
callback
).send();
}
void ApiWrap::requestAppChangelogs() {
auto oldAppVersion = Local::oldMapVersion();
if (oldAppVersion > 0 && oldAppVersion < AppVersion) {
_changelogSubscription = subscribe(_session->data().moreChatsLoaded(), [this, oldAppVersion] {
auto oldVersionString = qsl("%1.%2.%3").arg(oldAppVersion / 1000000).arg((oldAppVersion % 1000000) / 1000).arg(oldAppVersion % 1000);
request(MTPhelp_GetAppChangelog(MTP_string(oldVersionString))).done([this, oldAppVersion](const MTPUpdates &result) {
applyUpdates(result);
auto resultEmpty = true;
switch (result.type()) {
case mtpc_updateShortMessage:
case mtpc_updateShortChatMessage:
case mtpc_updateShort: resultEmpty = false; break;
case mtpc_updatesCombined: resultEmpty = result.c_updatesCombined().vupdates.v.isEmpty(); break;
case mtpc_updates: resultEmpty = result.c_updates().vupdates.v.isEmpty(); break;
case mtpc_updatesTooLong:
case mtpc_updateShortSentMessage: LOG(("API Error: Bad updates type in app changelog.")); break;
}
if (resultEmpty) {
addLocalChangelogs(oldAppVersion);
}
}).send();
unsubscribe(base::take(_changelogSubscription));
});
}
}
void ApiWrap::addLocalChangelogs(int oldAppVersion) {
auto addedSome = false;
auto addLocalChangelog = [this, &addedSome](const QString &text) {
auto textWithEntities = TextWithEntities { text };
TextUtilities::ParseEntities(textWithEntities, TextParseLinks);
App::wnd()->serviceNotification(textWithEntities, MTP_messageMediaEmpty(), unixtime());
addedSome = true;
};
if (cAlphaVersion() || cBetaVersion()) {
auto addLocalAlphaChangelog = [this, oldAppVersion, addLocalChangelog](int changeVersion, const char *changes) {
if (oldAppVersion < changeVersion) {
auto changeVersionString = QString::number(changeVersion / 1000000) + '.' + QString::number((changeVersion % 1000000) / 1000) + ((changeVersion % 1000) ? ('.' + QString::number(changeVersion % 1000)) : QString());
auto text = qsl("New in version %1:\n\n").arg(changeVersionString) + QString::fromUtf8(changes).trimmed();
addLocalChangelog(text);
}
};
addLocalAlphaChangelog(1001024, "\xE2\x80\x94 Radically improved navigation. New side panel on the right with quick access to shared media and group members.\n\xE2\x80\x94 Pinned Messages. If you are a channel admin, pin messages to focus your subscribers\xE2\x80\x99 attention on important announcements.\n\xE2\x80\x94 Also supported clearing history in supergroups and added a host of minor improvements.");
addLocalAlphaChangelog(1001026, "\xE2\x80\x94 Admin badges in supergroup messages.\n\xE2\x80\x94 Fix crashing on launch in OS X 10.6.\n\xE2\x80\x94 Bug fixes and other minor improvements.");
addLocalAlphaChangelog(1001027, "\xE2\x80\x94 Saved Messages. Bookmark messages by forwarding them to \xE2\x80\x9C""Saved Messages\xE2\x80\x9D. Access them from the Chats list or from the side menu.");
}
if (!addedSome) {
auto text = lng_new_version_wrap(lt_version, str_const_toString(AppVersionStr), lt_changes, lang(lng_new_version_minor), lt_link, qsl("https://desktop.telegram.org/changelog")).trimmed();
addLocalChangelog(text);
}
}
void ApiWrap::applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId) {
void ApiWrap::applyUpdates(
const MTPUpdates &updates,
uint64 sentMessageRandomId) {
App::main()->feedUpdates(updates, sentMessageRandomId);
}
void ApiWrap::sendMessageFail(const RPCError &error) {
if (error.type() == qstr("PEER_FLOOD")) {
Ui::show(Box<InformBox>(
PeerFloodErrorText(PeerFloodType::Send)));
} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
const auto link = textcmdLink(
Messenger::Instance().createInternalLinkFull(qsl("spambot")),
lang(lng_cant_more_info));
Ui::show(Box<InformBox>(lng_error_public_groups_denied(
lt_more_info,
link)));
}
}
void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback) {
auto &req = (channel ? _channelMessageDataRequests[channel][msgId] : _messageDataRequests[msgId]);
if (callback) {
@@ -845,47 +887,65 @@ void ApiWrap::requestSelfParticipant(ChannelData *channel) {
_selfParticipantRequests.insert(channel, requestId);
}
void ApiWrap::kickParticipant(PeerData *peer, UserData *user, const MTPChannelBannedRights &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(
@@ -946,7 +1006,7 @@ void ApiWrap::saveStickerSets(const Stickers::Order &localOrder, const Stickers:
request(base::take(_stickersClearRecentRequestId)).cancel();
auto writeInstalled = true, writeRecent = false, writeCloudRecent = false, writeFaved = false, writeArchived = false;
auto &recent = cGetRecentStickers();
auto &recent = Stickers::GetRecentPack();
auto &sets = Auth().data().stickerSetsRef();
_stickersOrder = localOrder;
@@ -1608,18 +1668,18 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
}
if (!v) return;
QMap<uint64, int32> msgsIds; // copied from feedMsgs
for (int32 i = 0, l = v->size(); i < l; ++i) {
const auto &msg(v->at(i));
switch (msg.type()) {
case mtpc_message: msgsIds.insert((uint64(uint32(msg.c_message().vid.v)) << 32) | uint64(i), i); break;
case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i); break;
case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i); break;
}
auto indices = base::flat_map<uint64, int>(); // copied from feedMsgs
for (auto i = 0, l = v->size(); i != l; ++i) {
const auto msgId = idFromMessage(v->at(i));
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
}
for_const (auto msgId, msgsIds) {
if (auto item = App::histories().addNewMessage(v->at(msgId), NewMessageExisting)) {
for (const auto [position, index] : indices) {
const auto item = App::histories().addNewMessage(
v->at(index),
NewMessageExisting);
if (item) {
item->setPendingInitDimensions();
}
}
@@ -1897,45 +1957,49 @@ void ApiWrap::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
case mtpc_updateShortMessage: {
auto &d = updates.c_updateShortMessage();
auto flags = mtpCastFlags(d.vflags.v) | MTPDmessage::Flag::f_from_id;
App::histories().addNewMessage(MTP_message(
MTP_flags(flags),
d.vid,
d.is_out() ? MTP_int(Auth().userId()) : d.vuser_id,
MTP_peerUser(d.is_out() ? d.vuser_id : MTP_int(Auth().userId())),
d.vfwd_from,
d.vvia_bot_id,
d.vreply_to_msg_id,
d.vdate,
d.vmessage,
MTP_messageMediaEmpty(),
MTPnullMarkup,
d.has_entities() ? d.ventities : MTPnullEntities,
MTPint(),
MTPint(),
MTPstring(),
MTPlong()), NewMessageUnread);
App::histories().addNewMessage(
MTP_message(
MTP_flags(flags),
d.vid,
d.is_out() ? MTP_int(Auth().userId()) : d.vuser_id,
MTP_peerUser(d.is_out() ? d.vuser_id : MTP_int(Auth().userId())),
d.vfwd_from,
d.vvia_bot_id,
d.vreply_to_msg_id,
d.vdate,
d.vmessage,
MTP_messageMediaEmpty(),
MTPnullMarkup,
d.has_entities() ? d.ventities : MTPnullEntities,
MTPint(),
MTPint(),
MTPstring(),
MTPlong()),
NewMessageUnread);
} break;
case mtpc_updateShortChatMessage: {
auto &d = updates.c_updateShortChatMessage();
auto flags = mtpCastFlags(d.vflags.v) | MTPDmessage::Flag::f_from_id;
App::histories().addNewMessage(MTP_message(
MTP_flags(flags),
d.vid,
d.vfrom_id,
MTP_peerChat(d.vchat_id),
d.vfwd_from,
d.vvia_bot_id,
d.vreply_to_msg_id,
d.vdate,
d.vmessage,
MTP_messageMediaEmpty(),
MTPnullMarkup,
d.has_entities() ? d.ventities : MTPnullEntities,
MTPint(),
MTPint(),
MTPstring(),
MTPlong()), NewMessageUnread);
App::histories().addNewMessage(
MTP_message(
MTP_flags(flags),
d.vid,
d.vfrom_id,
MTP_peerChat(d.vchat_id),
d.vfwd_from,
d.vvia_bot_id,
d.vreply_to_msg_id,
d.vdate,
d.vmessage,
MTP_messageMediaEmpty(),
MTPnullMarkup,
d.has_entities() ? d.ventities : MTPnullEntities,
MTPint(),
MTPint(),
MTPstring(),
MTPlong()),
NewMessageUnread);
} break;
case mtpc_updateShortSentMessage: {
@@ -2455,6 +2519,7 @@ void ApiWrap::forwardMessages(
}
auto forwardFrom = items.front()->history()->peer;
auto currentGroupId = items.front()->groupId();
auto ids = QVector<MTPint>();
auto randomIds = QVector<MTPlong>();
@@ -2462,8 +2527,12 @@ void ApiWrap::forwardMessages(
if (shared) {
++shared->requestsLeft;
}
const auto finalFlags = sendFlags
| (currentGroupId == MessageGroupId()
? MTPmessages_ForwardMessages::Flag(0)
: MTPmessages_ForwardMessages::Flag::f_grouped);
history->sendRequestId = request(MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
MTP_flags(finalFlags),
forwardFrom->input,
MTP_vector<MTPint>(ids),
MTP_vector<MTPlong>(randomIds),
@@ -2508,9 +2577,13 @@ void ApiWrap::forwardMessages(
App::historyRegRandom(randomId, newId);
}
}
if (forwardFrom != item->history()->peer) {
const auto newFrom = item->history()->peer;
const auto newGroupId = item->groupId();
if (forwardFrom != newFrom
|| currentGroupId != newGroupId) {
sendAccumulated();
forwardFrom = item->history()->peer;
forwardFrom = newFrom;
currentGroupId = newGroupId;
}
ids.push_back(MTP_int(item->id));
randomIds.push_back(MTP_long(randomId));
@@ -2556,17 +2629,12 @@ void ApiWrap::sendSharedContact(
const auto history = options.history;
const auto peer = history->peer;
const auto randomId = rand_value<uint64>();
const auto newId = FullMsgId(history->channelId(), clientMsgId());
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
const auto silentPost = channelPost && peer->notifySilentPosts();
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (options.replyTo) {
flags |= MTPDmessage::Flag::f_reply_to_msg_id;
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
}
if (channelPost) {
flags |= MTPDmessage::Flag::f_views;
@@ -2577,14 +2645,11 @@ void ApiWrap::sendSharedContact(
} else {
flags |= MTPDmessage::Flag::f_from_id;
}
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
const auto messageFromId = channelPost ? 0 : Auth().userId();
const auto messagePostAuthor = channelPost
? (Auth().user()->firstName + ' ' + Auth().user()->lastName)
: QString();
history->addNewMessage(
const auto item = history->addNewMessage(
MTP_message(
MTP_flags(flags),
MTP_int(newId.msg),
@@ -2608,35 +2673,358 @@ void ApiWrap::sendSharedContact(
MTPlong()),
NewMessageUnread);
const auto media = MTP_inputMediaContact(
MTP_string(phone),
MTP_string(firstName),
MTP_string(lastName));
sendMedia(item, media, peer->notifySilentPosts());
}
void ApiWrap::sendVoiceMessage(
QByteArray result,
VoiceWaveform waveform,
int duration,
const SendOptions &options) {
const auto caption = QString();
const auto to = FileLoadTaskOptions(options);
_fileLoader->addTask(std::make_unique<FileLoadTask>(
result,
duration,
waveform,
to,
caption));
}
void ApiWrap::sendFiles(
Storage::PreparedList &&list,
SendMediaType type,
QString caption,
std::shared_ptr<SendingAlbum> album,
const SendOptions &options) {
if (list.files.size() > 1 && !caption.isEmpty()) {
auto message = MainWidget::MessageToSend(options.history);
message.textWithTags = { caption, TextWithTags::Tags() };
message.replyTo = options.replyTo;
message.clearDraft = false;
App::main()->sendMessage(message);
caption = QString();
}
const auto to = FileLoadTaskOptions(options);
if (album) {
album->silent = to.silent;
}
auto tasks = std::vector<std::unique_ptr<Task>>();
tasks.reserve(list.files.size());
for (auto &file : list.files) {
if (album) {
switch (file.type) {
case Storage::PreparedFile::AlbumType::Photo:
type = SendMediaType::Photo;
break;
case Storage::PreparedFile::AlbumType::Video:
type = SendMediaType::File;
break;
default: Unexpected("AlbumType in uploadFilesAfterConfirmation");
}
}
tasks.push_back(std::make_unique<FileLoadTask>(
file.path,
file.content,
std::move(file.information),
type,
to,
caption,
album));
}
if (album) {
_sendingAlbums.emplace(album->groupId, album);
album->items.reserve(tasks.size());
for (const auto &task : tasks) {
album->items.push_back(SendingAlbum::Item(task->id()));
}
}
_fileLoader->addTasks(std::move(tasks));
}
void ApiWrap::sendFile(
const QByteArray &fileContent,
SendMediaType type,
const SendOptions &options) {
auto to = FileLoadTaskOptions(options);
auto caption = QString();
_fileLoader->addTask(std::make_unique<FileLoadTask>(
QString(),
fileContent,
nullptr,
type,
to,
caption));
}
void ApiWrap::sendUploadedPhoto(
FullMsgId localId,
const MTPInputFile &file,
bool silent) {
if (const auto item = App::histItemById(localId)) {
const auto caption = item->getMedia()
? item->getMedia()->getCaption()
: TextWithEntities();
const auto media = MTP_inputMediaUploadedPhoto(
MTP_flags(0),
file,
MTP_string(caption.text),
MTPVector<MTPInputDocument>(),
MTP_int(0));
if (const auto groupId = item->groupId()) {
uploadAlbumMedia(item, groupId, media);
} else {
sendMedia(item, media, silent);
}
}
}
void ApiWrap::sendUploadedDocument(
FullMsgId localId,
const MTPInputFile &file,
const base::optional<MTPInputFile> &thumb,
bool silent) {
if (const auto item = App::histItemById(localId)) {
auto media = item->getMedia();
if (auto document = media ? media->getDocument() : nullptr) {
const auto caption = media->getCaption();
const auto groupId = item->groupId();
const auto flags = MTPDinputMediaUploadedDocument::Flags(0)
| (thumb
? MTPDinputMediaUploadedDocument::Flag::f_thumb
: MTPDinputMediaUploadedDocument::Flag(0))
| (groupId
? MTPDinputMediaUploadedDocument::Flag::f_nosound_video
: MTPDinputMediaUploadedDocument::Flag(0));
const auto media = MTP_inputMediaUploadedDocument(
MTP_flags(flags),
file,
thumb ? *thumb : MTPInputFile(),
MTP_string(document->mimeString()),
ComposeSendingDocumentAttributes(document),
MTP_string(caption.text),
MTPVector<MTPInputDocument>(),
MTP_int(0));
if (groupId) {
uploadAlbumMedia(item, groupId, media);
} else {
sendMedia(item, media, silent);
}
}
}
}
void ApiWrap::cancelLocalItem(not_null<HistoryItem*> item) {
Expects(!IsServerMsgId(item->id));
if (const auto groupId = item->groupId()) {
sendAlbumWithCancelled(item, groupId);
}
}
void ApiWrap::uploadAlbumMedia(
not_null<HistoryItem*> item,
const MessageGroupId &groupId,
const MTPInputMedia &media) {
const auto localId = item->fullId();
const auto failed = [this] {
};
request(MTPmessages_UploadMedia(
item->history()->peer->input,
media
)).done([=](const MTPMessageMedia &result) {
const auto item = App::histItemById(localId);
if (!item) {
failed();
}
if (const auto media = item->getMedia()) {
if (const auto photo = media->getPhoto()) {
photo->setWaitingForAlbum();
} else if (const auto document = media->getDocument()) {
document->setWaitingForAlbum();
}
}
switch (result.type()) {
case mtpc_messageMediaPhoto: {
const auto &data = result.c_messageMediaPhoto();
if (data.vphoto.type() != mtpc_photo) {
failed();
return;
}
const auto &photo = data.vphoto.c_photo();
const auto flags = MTPDinputMediaPhoto::Flags(0)
| (data.has_ttl_seconds()
? MTPDinputMediaPhoto::Flag::f_ttl_seconds
: MTPDinputMediaPhoto::Flag(0));
const auto media = MTP_inputMediaPhoto(
MTP_flags(flags),
MTP_inputPhoto(photo.vid, photo.vaccess_hash),
data.has_caption() ? data.vcaption : MTP_string(QString()),
data.has_ttl_seconds() ? data.vttl_seconds : MTPint());
sendAlbumWithUploaded(item, groupId, media);
} break;
case mtpc_messageMediaDocument: {
const auto &data = result.c_messageMediaDocument();
if (data.vdocument.type() != mtpc_document) {
failed();
return;
}
const auto &document = data.vdocument.c_document();
const auto flags = MTPDinputMediaDocument::Flags(0)
| (data.has_ttl_seconds()
? MTPDinputMediaDocument::Flag::f_ttl_seconds
: MTPDinputMediaDocument::Flag(0));
const auto media = MTP_inputMediaDocument(
MTP_flags(flags),
MTP_inputDocument(document.vid, document.vaccess_hash),
data.has_caption() ? data.vcaption : MTP_string(QString()),
data.has_ttl_seconds() ? data.vttl_seconds : MTPint());
sendAlbumWithUploaded(item, groupId, media);
} break;
}
}).fail([=](const RPCError &error) {
failed();
}).send();
}
void ApiWrap::sendMedia(
not_null<HistoryItem*> item,
const MTPInputMedia &media,
bool silent) {
const auto randomId = rand_value<uint64>();
App::historyRegRandom(randomId, item->fullId());
sendMediaWithRandomId(item, media, silent, randomId);
}
void ApiWrap::sendMediaWithRandomId(
not_null<HistoryItem*> item,
const MTPInputMedia &media,
bool silent,
uint64 randomId) {
const auto history = item->history();
const auto replyTo = item->replyToId();
const auto flags = MTPmessages_SendMedia::Flags(0)
| (replyTo
? MTPmessages_SendMedia::Flag::f_reply_to_msg_id
: MTPmessages_SendMedia::Flag(0))
| (IsSilentPost(item, silent)
? MTPmessages_SendMedia::Flag::f_silent
: MTPmessages_SendMedia::Flag(0));
history->sendRequestId = request(MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
MTP_int(options.replyTo),
MTP_inputMediaContact(
MTP_string(phone),
MTP_string(firstName),
MTP_string(lastName)),
MTP_flags(flags),
history->peer->input,
MTP_int(replyTo),
media,
MTP_long(randomId),
MTPnullMarkup
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
}).fail([](const RPCError &error) {
if (error.type() == qstr("PEER_FLOOD")) {
Ui::show(Box<InformBox>(
PeerFloodErrorText(PeerFloodType::Send)));
} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
const auto link = textcmdLink(
Messenger::Instance().createInternalLinkFull(qsl("spambot")),
lang(lng_cant_more_info));
Ui::show(Box<InformBox>(lng_error_public_groups_denied(
lt_more_info,
link)));
}
}).afterRequest(
history->sendRequestId
)).done([=](const MTPUpdates &result) { applyUpdates(result);
}).fail([=](const RPCError &error) { sendMessageFail(error);
}).afterRequest(history->sendRequestId
).send();
}
App::historyRegRandom(randomId, newId);
void ApiWrap::sendAlbumWithUploaded(
not_null<HistoryItem*> item,
const MessageGroupId &groupId,
const MTPInputMedia &media) {
const auto localId = item->fullId();
const auto randomId = rand_value<uint64>();
App::historyRegRandom(randomId, localId);
const auto albumIt = _sendingAlbums.find(groupId.raw());
Assert(albumIt != _sendingAlbums.end());
const auto &album = albumIt->second;
const auto proj = [](const SendingAlbum::Item &item) {
return item.msgId;
};
const auto itemIt = ranges::find(album->items, localId, proj);
Assert(itemIt != album->items.end());
Assert(!itemIt->media);
itemIt->media = MTP_inputSingleMedia(media, MTP_long(randomId));
sendAlbumIfReady(album.get());
}
void ApiWrap::sendAlbumWithCancelled(
not_null<HistoryItem*> item,
const MessageGroupId &groupId) {
const auto localId = item->fullId();
const auto albumIt = _sendingAlbums.find(groupId.raw());
Assert(albumIt != _sendingAlbums.end());
const auto &album = albumIt->second;
const auto proj = [](const SendingAlbum::Item &item) {
return item.msgId;
};
const auto itemIt = ranges::find(album->items, localId, proj);
Assert(itemIt != album->items.end());
album->items.erase(itemIt);
sendAlbumIfReady(album.get());
}
void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
const auto groupId = album->groupId;
if (album->items.empty()) {
_sendingAlbums.remove(groupId);
return;
}
auto sample = (HistoryItem*)nullptr;
auto medias = QVector<MTPInputSingleMedia>();
medias.reserve(album->items.size());
for (const auto &item : album->items) {
if (!item.media) {
return;
} else if (!sample) {
sample = App::histItemById(item.msgId);
}
medias.push_back(*item.media);
}
if (!sample) {
_sendingAlbums.remove(groupId);
return;
} else if (medias.size() < 2) {
const auto &single = medias.front().c_inputSingleMedia();
sendMediaWithRandomId(
sample,
single.vmedia,
album->silent,
single.vrandom_id.v);
_sendingAlbums.remove(groupId);
return;
}
const auto history = sample->history();
const auto replyTo = sample->replyToId();
const auto flags = MTPmessages_SendMultiMedia::Flags(0)
| (replyTo
? MTPmessages_SendMultiMedia::Flag::f_reply_to_msg_id
: MTPmessages_SendMultiMedia::Flag(0))
| (IsSilentPost(sample, album->silent)
? MTPmessages_SendMultiMedia::Flag::f_silent
: MTPmessages_SendMultiMedia::Flag(0));
history->sendRequestId = request(MTPmessages_SendMultiMedia(
MTP_flags(flags),
history->peer->input,
MTP_int(replyTo),
MTP_vector<MTPInputSingleMedia>(medias)
)).done([=](const MTPUpdates &result) {
_sendingAlbums.remove(groupId);
applyUpdates(result);
}).fail([=](const RPCError &error) {
_sendingAlbums.remove(groupId);
sendMessageFail(error);
}).afterRequest(history->sendRequestId
).send();
}
void ApiWrap::readServerHistory(not_null<History*> history) {

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"
@@ -46,6 +47,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "numbers.h"
#include "observer_peer.h"
#include "auth_session.h"
#include "core/crash_reports.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "window/themes/window_theme.h"
@@ -127,7 +129,8 @@ namespace {
LastPhotosList lastPhotos;
using LastPhotosMap = QHash<PhotoData*, LastPhotosList::iterator>;
LastPhotosMap lastPhotosMap;
}
} // namespace
namespace App {
@@ -218,117 +221,6 @@ namespace {
}
}
TimeId onlineForSort(UserData *user, TimeId now) {
if (isServiceUser(user->id) || user->botInfo) {
return -1;
}
TimeId online = user->onlineTill;
if (online <= 0) {
switch (online) {
case 0:
case -1: return online;
case -2: {
QDate yesterday(date(now).date());
return int32(QDateTime(yesterday.addDays(-3)).toTime_t()) + (unixtime() - myunixtime());
} break;
case -3: {
QDate weekago(date(now).date());
return int32(QDateTime(weekago.addDays(-7)).toTime_t()) + (unixtime() - myunixtime());
} break;
case -4: {
QDate monthago(date(now).date());
return int32(QDateTime(monthago.addDays(-30)).toTime_t()) + (unixtime() - myunixtime());
} break;
}
return -online;
}
return online;
}
int32 onlineWillChangeIn(UserData *user, TimeId now) {
if (isServiceUser(user->id) || user->botInfo) {
return 86400;
}
return onlineWillChangeIn(user->onlineTill, now);
}
int32 onlineWillChangeIn(TimeId online, TimeId now) {
if (online <= 0) {
if (-online > now) return std::max(-online - now, 86400);
return 86400;
}
if (online > now) {
return std::max(online - now, 86400);
}
int32 minutes = (now - online) / 60;
if (minutes < 60) {
return (minutes + 1) * 60 - (now - online);
}
int32 hours = (now - online) / 3600;
if (hours < 12) {
return (hours + 1) * 3600 - (now - online);
}
QDateTime dNow(date(now)), dTomorrow(dNow.date().addDays(1));
return std::max(dNow.secsTo(dTomorrow), 86400LL);
}
QString onlineText(UserData *user, TimeId now, bool precise) {
if (isNotificationsUser(user->id)) {
return lang(lng_status_service_notifications);
} else if (user->botInfo) {
return lang(lng_status_bot);
} else if (isServiceUser(user->id)) {
return lang(lng_status_support);
}
return onlineText(user->onlineTill, now, precise);
}
QString onlineText(TimeId online, TimeId now, bool precise) {
if (online <= 0) {
switch (online) {
case 0:
case -1: return lang(lng_status_offline);
case -2: return lang(lng_status_recently);
case -3: return lang(lng_status_last_week);
case -4: return lang(lng_status_last_month);
}
return (-online > now) ? lang(lng_status_online) : lang(lng_status_recently);
}
if (online > now) {
return lang(lng_status_online);
}
QString when;
if (precise) {
QDateTime dOnline(date(online)), dNow(date(now));
if (dOnline.date() == dNow.date()) {
return lng_status_lastseen_today(lt_time, dOnline.time().toString(cTimeFormat()));
} else if (dOnline.date().addDays(1) == dNow.date()) {
return lng_status_lastseen_yesterday(lt_time, dOnline.time().toString(cTimeFormat()));
}
return lng_status_lastseen_date_time(lt_date, dOnline.date().toString(qsl("dd.MM.yy")), lt_time, dOnline.time().toString(cTimeFormat()));
}
int32 minutes = (now - online) / 60;
if (!minutes) {
return lang(lng_status_lastseen_now);
} else if (minutes < 60) {
return lng_status_lastseen_minutes(lt_count, minutes);
}
int32 hours = (now - online) / 3600;
if (hours < 12) {
return lng_status_lastseen_hours(lt_count, hours);
}
QDateTime dOnline(date(online)), dNow(date(now));
if (dOnline.date() == dNow.date()) {
return lng_status_lastseen_today(lt_time, dOnline.time().toString(cTimeFormat()));
} else if (dOnline.date().addDays(1) == dNow.date()) {
return lng_status_lastseen_yesterday(lt_time, dOnline.time().toString(cTimeFormat()));
}
return lng_status_lastseen_date(lt_date, dOnline.date().toString(qsl("dd.MM.yy")));
}
namespace {
// we should get a full restriction in "{fulltype}: {reason}" format and we
// need to find a "-all" tag in {fulltype}, otherwise ignore this restriction
@@ -353,27 +245,6 @@ namespace {
}
}
bool onlineColorUse(UserData *user, TimeId now) {
if (isServiceUser(user->id) || user->botInfo) {
return false;
}
return onlineColorUse(user->onlineTill, now);
}
bool onlineColorUse(TimeId online, TimeId now) {
if (online <= 0) {
switch (online) {
case 0:
case -1:
case -2:
case -3:
case -4: return false;
}
return (-online > now);
}
return (online > now);
}
UserData *feedUser(const MTPUser &user) {
UserData *data = nullptr;
bool wasContact = false, minimal = false;
@@ -472,7 +343,7 @@ namespace {
QString pname = (showPhoneChanged || phoneChanged || nameChanged) ? ((showPhone && !phone.isEmpty()) ? formatPhone(phone) : QString()) : data->nameOrPhone;
if (!minimal && d.is_self() && uname != data->username) {
SignalHandlers::setCrashAnnotation("Username", uname);
CrashReports::SetAnnotation("Username", uname);
}
data->setName(fname, lname, pname, uname);
if (d.has_photo()) {
@@ -648,26 +519,34 @@ namespace {
}
} break;
case mtpc_channel: {
auto &d = chat.c_channel();
const auto &d = chat.c_channel();
auto peerId = peerFromChannel(d.vid.v);
const auto peerId = peerFromChannel(d.vid.v);
minimal = d.is_min();
if (minimal) {
data = App::channelLoaded(peerId);
if (!data) {
return nullptr; // minimal is not loaded, need to make getDifference
// Can't apply minimal to a not loaded channel.
// Need to make getDifference.
return nullptr;
}
} else {
data = App::channel(peerId);
data->input = MTP_inputPeerChannel(d.vid, d.has_access_hash() ? d.vaccess_hash : MTP_long(0));
const auto accessHash = d.has_access_hash()
? d.vaccess_hash
: MTP_long(0);
data->input = MTP_inputPeerChannel(d.vid, accessHash);
}
auto cdata = data->asChannel();
auto wasInChannel = cdata->amIn();
auto canViewAdmins = cdata->canViewAdmins();
auto canViewMembers = cdata->canViewMembers();
auto canAddMembers = cdata->canAddMembers();
const auto cdata = data->asChannel();
const auto wasInChannel = cdata->amIn();
const auto canViewAdmins = cdata->canViewAdmins();
const auto canViewMembers = cdata->canViewMembers();
const auto canAddMembers = cdata->canAddMembers();
if (d.has_participants_count()) {
cdata->setMembersCount(d.vparticipants_count.v);
}
if (minimal) {
auto mask = 0
| MTPDchannel::Flag::f_broadcast
@@ -1033,26 +912,22 @@ namespace {
if (m.has_from_id() && peerId == Auth().userPeerId()) {
peerId = peerFromUser(m.vfrom_id);
}
if (auto existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
if (const auto existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
auto text = qs(m.vmessage);
auto entities = m.has_entities() ? TextUtilities::EntitiesFromMTP(m.ventities.v) : EntitiesInText();
auto entities = m.has_entities()
? TextUtilities::EntitiesFromMTP(m.ventities.v)
: EntitiesInText();
existing->setText({ text, entities });
existing->updateMedia(m.has_media() ? (&m.vmedia) : nullptr);
existing->updateReplyMarkup(m.has_reply_markup() ? (&m.vreply_markup) : nullptr);
existing->updateReplyMarkup(m.has_reply_markup()
? (&m.vreply_markup)
: nullptr);
existing->setViewsCount(m.has_views() ? m.vviews.v : -1);
existing->addToUnreadMentions(AddToUnreadMentionsMethod::New);
if (auto sharedMediaTypes = existing->sharedMediaTypes()) {
Auth().storage().add(Storage::SharedMediaAddNew(
peerId,
sharedMediaTypes,
existing->id));
}
existing->indexAsNewItem();
if (!existing->detached()) {
App::checkSavedGif(existing);
return true;
}
return false;
}
return false;
@@ -1104,29 +979,23 @@ namespace {
}
void feedMsgs(const QVector<MTPMessage> &msgs, NewMessageType type) {
QMap<uint64, int32> msgsIds;
for (int32 i = 0, l = msgs.size(); i < l; ++i) {
const auto &msg(msgs.at(i));
switch (msg.type()) {
case mtpc_message: {
const auto &d(msg.c_message());
bool needToAdd = true;
auto indices = base::flat_map<uint64, int>();
for (int i = 0, l = msgs.size(); i != l; ++i) {
const auto &msg = msgs[i];
if (msg.type() == mtpc_message) {
const auto &data = msg.c_message();
if (type == NewMessageUnread) { // new message, index my forwarded messages to links overview
if (checkEntitiesAndViewsUpdate(d)) { // already in blocks
if (checkEntitiesAndViewsUpdate(data)) { // already in blocks
LOG(("Skipping message, because it is already in blocks!"));
needToAdd = false;
continue;
}
}
if (needToAdd) {
msgsIds.insert((uint64(uint32(d.vid.v)) << 32) | uint64(i), i);
}
} break;
case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i); break;
case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i); break;
}
const auto msgId = idFromMessage(msg);
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
}
for (QMap<uint64, int32>::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) {
histories().addNewMessage(msgs.at(i.value()), type);
for (const auto [position, index] : indices) {
histories().addNewMessage(msgs[index], type);
}
}
@@ -1421,7 +1290,23 @@ namespace {
}
WebPageData *feedWebPage(const MTPDwebPagePending &webpage, WebPageData *convert) {
return App::webPageSet(webpage.vid.v, convert, QString(), QString(), QString(), QString(), QString(), TextWithEntities(), nullptr, nullptr, 0, QString(), webpage.vdate.v);
constexpr auto kDefaultPendingTimeout = 60;
return App::webPageSet(
webpage.vid.v,
convert,
QString(),
QString(),
QString(),
QString(),
QString(),
TextWithEntities(),
nullptr,
nullptr,
0,
QString(),
webpage.vdate.v
? webpage.vdate.v
: (unixtime() + kDefaultPendingTimeout));
}
WebPageData *feedWebPage(const MTPWebPage &webpage) {
@@ -1514,22 +1399,29 @@ namespace {
}
PhotoData *photo(const PhotoId &photo) {
PhotosData::const_iterator i = ::photosData.constFind(photo);
auto i = ::photosData.constFind(photo);
if (i == ::photosData.cend()) {
i = ::photosData.insert(photo, new PhotoData(photo));
}
return i.value();
}
PhotoData *photoSet(const PhotoId &photo, PhotoData *convert, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full) {
PhotoData *photoSet(
const PhotoId &photo,
PhotoData *convert,
const uint64 &access,
int32 date,
const ImagePtr &thumb,
const ImagePtr &medium,
const ImagePtr &full) {
if (convert) {
if (convert->id != photo) {
PhotosData::iterator i = ::photosData.find(convert->id);
const auto i = ::photosData.find(convert->id);
if (i != ::photosData.cend() && i.value() == convert) {
::photosData.erase(i);
}
convert->id = photo;
convert->uploadingData.reset();
convert->uploadingData = nullptr;
}
if (date) {
convert->access = access;
@@ -1539,9 +1431,9 @@ namespace {
updateImage(convert->full, full);
}
}
PhotosData::const_iterator i = ::photosData.constFind(photo);
const auto i = ::photosData.constFind(photo);
PhotoData *result;
LastPhotosMap::iterator inLastIter = lastPhotosMap.end();
auto inLastIter = lastPhotosMap.end();
if (i == ::photosData.cend()) {
if (convert) {
result = convert;
@@ -1575,27 +1467,39 @@ namespace {
}
DocumentData *document(const DocumentId &document) {
DocumentsData::const_iterator i = ::documentsData.constFind(document);
auto i = ::documentsData.constFind(document);
if (i == ::documentsData.cend()) {
i = ::documentsData.insert(document, DocumentData::create(document));
}
return i.value();
}
DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 version, int32 date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation) {
DocumentData *documentSet(
const DocumentId &document,
DocumentData *convert,
const uint64 &access,
int32 version,
int32 date,
const QVector<MTPDocumentAttribute> &attributes,
const QString &mime,
const ImagePtr &thumb,
int32 dc,
int32 size,
const StorageImageLocation &thumbLocation) {
bool versionChanged = false;
bool sentSticker = false;
if (convert) {
MediaKey oldKey = convert->mediaKey();
bool idChanged = (convert->id != document);
const auto oldKey = convert->mediaKey();
const auto idChanged = (convert->id != document);
if (idChanged) {
DocumentsData::iterator i = ::documentsData.find(convert->id);
const auto i = ::documentsData.find(convert->id);
if (i != ::documentsData.cend() && i.value() == convert) {
::documentsData.erase(i);
}
convert->id = document;
convert->status = FileReady;
convert->uploadingData = nullptr;
sentSticker = (convert->sticker() != 0);
}
if (date) {
@@ -1613,7 +1517,7 @@ namespace {
convert->sticker()->loc = thumbLocation;
}
MediaKey newKey = convert->mediaKey();
const auto newKey = convert->mediaKey();
if (idChanged) {
if (convert->isVoiceMessage()) {
Local::copyAudio(oldKey, newKey);
@@ -1627,7 +1531,7 @@ namespace {
Local::writeSavedGifs();
}
}
DocumentsData::const_iterator i = ::documentsData.constFind(document);
const auto i = ::documentsData.constFind(document);
DocumentData *result;
if (i == ::documentsData.cend()) {
if (convert) {
@@ -1704,40 +1608,60 @@ namespace {
return i.value();
}
WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const TextWithEntities &description, PhotoData *photo, DocumentData *document, int32 duration, const QString &author, int32 pendingTill) {
WebPageData *webPageSet(
const WebPageId &webPage,
WebPageData *convert,
const QString &type,
const QString &url,
const QString &displayUrl,
const QString &siteName,
const QString &title,
const TextWithEntities &description,
PhotoData *photo,
DocumentData *document,
int duration,
const QString &author,
int pendingTill) {
if (convert) {
if (convert->id != webPage) {
auto i = webPagesData.find(convert->id);
const auto i = webPagesData.find(convert->id);
if (i != webPagesData.cend() && i.value() == convert) {
webPagesData.erase(i);
}
convert->id = webPage;
}
if ((convert->url.isEmpty() && !url.isEmpty()) || (convert->pendingTill && convert->pendingTill != pendingTill && pendingTill >= -1)) {
convert->type = toWebPageType(type);
convert->url = TextUtilities::Clean(url);
convert->displayUrl = TextUtilities::Clean(displayUrl);
convert->siteName = TextUtilities::Clean(siteName);
convert->title = TextUtilities::SingleLine(title);
convert->description = description;
convert->photo = photo;
convert->document = document;
convert->duration = duration;
convert->author = TextUtilities::Clean(author);
if (convert->pendingTill > 0 && pendingTill <= 0) {
Auth().api().clearWebPageRequest(convert);
}
convert->pendingTill = pendingTill;
if (App::main()) App::main()->webPageUpdated(convert);
}
convert->applyChanges(
type,
url,
displayUrl,
siteName,
title,
description,
photo,
document,
duration,
author,
pendingTill);
}
auto i = webPagesData.constFind(webPage);
const auto i = webPagesData.constFind(webPage);
WebPageData *result;
if (i == webPagesData.cend()) {
if (convert) {
result = convert;
} else {
result = new WebPageData(webPage, toWebPageType(type), url, displayUrl, siteName, title, description, document, photo, duration, author, (pendingTill >= -1) ? pendingTill : -1);
result = new WebPageData(
webPage,
toWebPageType(type),
url,
displayUrl,
siteName,
title,
description,
document,
photo,
duration,
author,
(pendingTill >= -1) ? pendingTill : -1);
if (pendingTill > 0) {
Auth().api().requestWebPageDelayed(result);
}
@@ -1746,23 +1670,18 @@ namespace {
} else {
result = i.value();
if (result != convert) {
if ((result->url.isEmpty() && !url.isEmpty()) || (result->pendingTill && result->pendingTill != pendingTill && pendingTill >= -1)) {
result->type = toWebPageType(type);
result->url = TextUtilities::Clean(url);
result->displayUrl = TextUtilities::Clean(displayUrl);
result->siteName = TextUtilities::Clean(siteName);
result->title = TextUtilities::SingleLine(title);
result->description = description;
result->photo = photo;
result->document = document;
result->duration = duration;
result->author = TextUtilities::Clean(author);
if (result->pendingTill > 0 && pendingTill <= 0) {
Auth().api().clearWebPageRequest(result);
}
result->pendingTill = pendingTill;
if (App::main()) App::main()->webPageUpdated(result);
}
result->applyChanges(
type,
url,
displayUrl,
siteName,
title,
description,
photo,
document,
duration,
author,
pendingTill);
}
}
return result;
@@ -2122,15 +2041,6 @@ namespace {
}
}
int msgRadius() {
static int MsgRadius = ([]() {
return st::historyMessageRadius;
auto minMsgHeight = (st::msgPadding.top() + st::msgFont->height + st::msgPadding.bottom());
return minMsgHeight / 2;
})();
return MsgRadius;
}
void createMaskCorners() {
QImage mask[4];
prepareCorners(SmallMaskCorners, st::buttonRadius, QColor(255, 255, 255), nullptr, mask);
@@ -2138,7 +2048,7 @@ namespace {
::cornersMaskSmall[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied);
::cornersMaskSmall[i].setDevicePixelRatio(cRetinaFactor());
}
prepareCorners(LargeMaskCorners, msgRadius(), QColor(255, 255, 255), nullptr, mask);
prepareCorners(LargeMaskCorners, st::historyMessageRadius, QColor(255, 255, 255), nullptr, mask);
for (int i = 0; i < 4; ++i) {
::cornersMaskLarge[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied);
::cornersMaskLarge[i].setDevicePixelRatio(cRetinaFactor());
@@ -2152,12 +2062,12 @@ namespace {
prepareCorners(StickerCorners, st::dateRadius, st::msgServiceBg);
prepareCorners(StickerSelectedCorners, st::dateRadius, st::msgServiceBgSelected);
prepareCorners(SelectedOverlaySmallCorners, st::buttonRadius, st::msgSelectOverlay);
prepareCorners(SelectedOverlayLargeCorners, msgRadius(), st::msgSelectOverlay);
prepareCorners(SelectedOverlayLargeCorners, st::historyMessageRadius, st::msgSelectOverlay);
prepareCorners(DateCorners, st::dateRadius, st::msgDateImgBg);
prepareCorners(DateSelectedCorners, st::dateRadius, st::msgDateImgBgSelected);
prepareCorners(InShadowCorners, msgRadius(), st::msgInShadow);
prepareCorners(InSelectedShadowCorners, msgRadius(), st::msgInShadowSelected);
prepareCorners(ForwardCorners, msgRadius(), st::historyForwardChooseBg);
prepareCorners(InShadowCorners, st::historyMessageRadius, st::msgInShadow);
prepareCorners(InSelectedShadowCorners, st::historyMessageRadius, st::msgInShadowSelected);
prepareCorners(ForwardCorners, st::historyMessageRadius, st::historyForwardChooseBg);
prepareCorners(MediaviewSaveCorners, st::mediaviewControllerRadius, st::mediaviewSaveMsgBg);
prepareCorners(EmojiHoverCorners, st::buttonRadius, st::emojiPanHover);
prepareCorners(StickerHoverCorners, st::buttonRadius, st::emojiPanHover);
@@ -2169,10 +2079,10 @@ namespace {
prepareCorners(Doc3Corners, st::buttonRadius, st::msgFile3Bg);
prepareCorners(Doc4Corners, st::buttonRadius, st::msgFile4Bg);
prepareCorners(MessageInCorners, msgRadius(), st::msgInBg, &st::msgInShadow);
prepareCorners(MessageInSelectedCorners, msgRadius(), st::msgInBgSelected, &st::msgInShadowSelected);
prepareCorners(MessageOutCorners, msgRadius(), st::msgOutBg, &st::msgOutShadow);
prepareCorners(MessageOutSelectedCorners, msgRadius(), st::msgOutBgSelected, &st::msgOutShadowSelected);
prepareCorners(MessageInCorners, st::historyMessageRadius, st::msgInBg, &st::msgInShadow);
prepareCorners(MessageInSelectedCorners, st::historyMessageRadius, st::msgInBgSelected, &st::msgInShadowSelected);
prepareCorners(MessageOutCorners, st::historyMessageRadius, st::msgOutBg, &st::msgOutShadow);
prepareCorners(MessageOutSelectedCorners, st::historyMessageRadius, st::msgOutBgSelected, &st::msgOutShadowSelected);
}
void createCorners() {
@@ -2647,42 +2557,46 @@ namespace {
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
}
void complexAdjustRect(ImageRoundCorners corners, QRect &rect, RectParts &parts) {
if (corners & ImageRoundCorner::TopLeft) {
if (!(corners & ImageRoundCorner::BottomLeft)) {
parts = RectPart::NoTopBottom | RectPart::FullTop;
rect.setHeight(rect.height() + msgRadius());
void rectWithCorners(Painter &p, QRect rect, const style::color &bg, RoundCorners index, RectParts corners) {
auto parts = RectPart::Top
| RectPart::NoTopBottom
| RectPart::Bottom
| corners;
roundRect(p, rect, bg, index, nullptr, parts);
if ((corners & RectPart::AllCorners) != RectPart::AllCorners) {
const auto size = ::corners[index].p[0].width() / cIntRetinaFactor();
if (!(corners & RectPart::TopLeft)) {
p.fillRect(rect.x(), rect.y(), size, size, bg);
}
if (!(corners & RectPart::TopRight)) {
p.fillRect(rect.x() + rect.width() - size, rect.y(), size, size, bg);
}
if (!(corners & RectPart::BottomLeft)) {
p.fillRect(rect.x(), rect.y() + rect.height() - size, size, size, bg);
}
if (!(corners & RectPart::BottomRight)) {
p.fillRect(rect.x() + rect.width() - size, rect.y() + rect.height() - size, size, size, bg);
}
} else if (corners & ImageRoundCorner::BottomLeft) {
parts = RectPart::NoTopBottom | RectPart::FullBottom;
rect.setTop(rect.y() - msgRadius());
} else {
parts = RectPart::NoTopBottom;
rect.setTop(rect.y() - msgRadius());
rect.setHeight(rect.height() + msgRadius());
}
}
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners) {
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners) {
if (radius == ImageRoundRadius::Ellipse) {
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(p.textPalette().selectOverlay);
p.drawEllipse(rect);
} else {
auto overlayCorners = (radius == ImageRoundRadius::Small) ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners;
auto overlayParts = RectPart::Full | RectPart::None;
if (radius == ImageRoundRadius::Large) {
complexAdjustRect(corners, rect, overlayParts);
}
roundRect(p, rect, p.textPalette().selectOverlay, overlayCorners, nullptr, overlayParts);
auto overlayCorners = (radius == ImageRoundRadius::Small)
? SelectedOverlaySmallCorners
: SelectedOverlayLargeCorners;
const auto bg = p.textPalette().selectOverlay;
rectWithCorners(p, rect, bg, overlayCorners, corners);
}
}
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners) {
auto parts = RectPart::Full | RectPart::None;
complexAdjustRect(corners, rect, parts);
roundRect(p, rect, st::msgInBg, MessageInCorners, nullptr, parts);
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners) {
rectWithCorners(p, rect, st::msgInBg, MessageInCorners, corners);
}
QImage *cornersMask(ImageRoundRadius radius) {
@@ -2766,7 +2680,7 @@ namespace {
QImage images[4];
switch (radius) {
case ImageRoundRadius::Small: prepareCorners(SmallMaskCorners, st::buttonRadius, bg, nullptr, images); break;
case ImageRoundRadius::Large: prepareCorners(LargeMaskCorners, msgRadius(), bg, nullptr, images); break;
case ImageRoundRadius::Large: prepareCorners(LargeMaskCorners, st::historyMessageRadius, bg, nullptr, images); break;
default: p.fillRect(x, y, w, h, bg); return;
}

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);
@@ -141,13 +133,52 @@ namespace App {
PeerData *peerByName(const QString &username);
QString peerName(const PeerData *peer, bool forDialogs = false);
PhotoData *photo(const PhotoId &photo);
PhotoData *photoSet(const PhotoId &photo, PhotoData *convert, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full);
PhotoData *photoSet(
const PhotoId &photo,
PhotoData *convert,
const uint64 &access,
int32 date,
const ImagePtr &thumb,
const ImagePtr &medium,
const ImagePtr &full);
DocumentData *document(const DocumentId &document);
DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 version, int32 date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation);
DocumentData *documentSet(
const DocumentId &document,
DocumentData *convert,
const uint64 &access,
int32 version,
int32 date,
const QVector<MTPDocumentAttribute> &attributes,
const QString &mime,
const ImagePtr &thumb,
int32 dc,
int32 size,
const StorageImageLocation &thumbLocation);
WebPageData *webPage(const WebPageId &webPage);
WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const TextWithEntities &description, PhotoData *photo, DocumentData *doc, int32 duration, const QString &author, int32 pendingTill);
WebPageData *webPageSet(
const WebPageId &webPage,
WebPageData *convert,
const QString &type,
const QString &url,
const QString &displayUrl,
const QString &siteName,
const QString &title,
const TextWithEntities &description,
PhotoData *photo,
DocumentData *document,
int duration,
const QString &author,
int pendingTill);
GameData *game(const GameId &game);
GameData *gameSet(const GameId &game, GameData *convert, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *doc);
GameData *gameSet(
const GameId &game,
GameData *convert,
const uint64 &accessHash,
const QString &shortName,
const QString &title,
const QString &description,
PhotoData *photo,
DocumentData *document);
LocationData *location(const LocationCoords &coords);
void forgetMedia();
@@ -270,8 +301,8 @@ namespace App {
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
void setProxySettings(QTcpSocket &socket);
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners);
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners);
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners);
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners);
QImage *cornersMask(ImageRoundRadius radius);
void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, RoundCorners index, const style::color *shadow = nullptr, RectParts parts = RectPart::Full);

View File

@@ -26,8 +26,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "storage/localstorage.h"
#include "autoupdater.h"
#include "window/notifications_manager.h"
#include "core/crash_reports.h"
#include "messenger.h"
#include "base/timer.h"
#include "core/crash_report_window.h"
namespace {
@@ -71,8 +73,13 @@ QString _escapeFrom7bit(const QString &str) {
} // namespace
Application::Application(int &argc, char **argv) : QApplication(argc, argv) {
QByteArray d(QFile::encodeName(QDir(cWorkingDir()).absolutePath()));
Application::Application(
not_null<Core::Launcher*> launcher,
int &argc,
char **argv)
: QApplication(argc, argv)
, _launcher(launcher) {
const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath());
char h[33] = { 0 };
hashMd5Hex(d.constData(), d.size(), h);
#ifndef OS_MAC_STORE
@@ -206,12 +213,12 @@ void Application::singleInstanceChecked() {
if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
new NotStartedWindow();
} else {
SignalHandlers::Status status = SignalHandlers::start();
if (status == SignalHandlers::CantOpen) {
const auto status = CrashReports::Start();
if (status == CrashReports::CantOpen) {
new NotStartedWindow();
} else if (status == SignalHandlers::LastCrashed) {
} else if (status == CrashReports::LastCrashed) {
if (Sandbox::LastCrashDump().isEmpty()) { // don't handle bad closing for now
if (SignalHandlers::restart() == SignalHandlers::CantOpen) {
if (CrashReports::Restart() == CrashReports::CantOpen) {
new NotStartedWindow();
} else {
Sandbox::launch();
@@ -313,7 +320,7 @@ void Application::startApplication() {
void Application::createMessenger() {
Expects(!App::quitting());
_messengerInstance = std::make_unique<Messenger>();
_messengerInstance = std::make_unique<Messenger>(_launcher);
}
void Application::closeApplication() {

View File

@@ -21,11 +21,16 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
class UpdateChecker;
namespace Core {
class Launcher;
} // namespace Core
class Application : public QApplication {
Q_OBJECT
public:
Application(int &argc, char **argv);
Application(not_null<Core::Launcher*> launcher, int &argc, char **argv);
bool event(QEvent *e) override;
@@ -55,6 +60,7 @@ private:
typedef QPair<QLocalSocket*, QByteArray> LocalClient;
typedef QList<LocalClient> LocalClients;
not_null<Core::Launcher*> _launcher;
std::unique_ptr<Messenger> _messengerInstance;
QString _localServerName, _localSocketReadData;

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) {
@@ -378,6 +389,13 @@ MessageIdsList AuthSessionData::itemsToIds(
}) | ranges::to_vector;
}
MessageIdsList AuthSessionData::groupToIds(
not_null<HistoryMessageGroup*> group) const {
auto result = itemsToIds(group->others);
result.push_back(group->leader->fullId());
return result;
}
AuthSession &Auth() {
auto result = Messenger::Instance().authSession();
Assert(result != nullptr);
@@ -392,8 +410,10 @@ AuthSession::AuthSession(UserId userId)
, _downloader(std::make_unique<Storage::Downloader>())
, _uploader(std::make_unique<Storage::Uploader>())
, _storage(std::make_unique<Storage::Facade>())
, _notifications(std::make_unique<Window::Notifications::System>(this)) {
, _notifications(std::make_unique<Window::Notifications::System>(this))
, _changelogs(Core::Changelogs::Create(this)) {
Expects(_userId != 0);
_saveDataTimer.setCallback([this] {
Local::writeUserSettings();
});
@@ -401,7 +421,7 @@ AuthSession::AuthSession(UserId userId)
_shouldLockAt = 0;
notifications().updateAll();
});
_api->start();
Window::Theme::Background()->start();
}
bool AuthSession::Exists() {

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;
}
@@ -263,6 +274,7 @@ public:
HistoryItemsList idsToItems(const MessageIdsList &ids) const;
MessageIdsList itemsToIds(const HistoryItemsList &items) const;
MessageIdsList groupToIds(not_null<HistoryMessageGroup*> group) const;
private:
struct Variables {
@@ -272,6 +284,7 @@ private:
static constexpr auto kDefaultThirdColumnWidth = 0;
bool lastSeenWarningSeen = false;
SendFilesWay sendFilesWay;
ChatHelpers::SelectorTab selectorTab; // per-window
bool tabbedSelectorSectionEnabled = false; // per-window
int tabbedSelectorSectionTooltipShown = 0;
@@ -405,5 +418,6 @@ private:
const std::unique_ptr<Storage::Uploader> _uploader;
const std::unique_ptr<Storage::Facade> _storage;
const std::unique_ptr<Window::Notifications::System> _notifications;
const std::unique_ptr<Core::Changelogs> _changelogs;
};

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

@@ -1,393 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "base/task_queue.h"
#include <thread>
#include <condition_variable>
namespace base {
namespace {
auto MainThreadId = std::this_thread::get_id();
const auto MaxThreadsCount = qMax(std::thread::hardware_concurrency(), 2U);
} // namespace
class TaskQueue::TaskQueueList {
public:
TaskQueueList();
void Register(TaskQueue *queue);
void Unregister(TaskQueue *queue);
bool IsInList(TaskQueue *queue) const;
void Clear();
bool Empty(int list_index_) const;
TaskQueue *TakeFirst(int list_index_);
private:
void Insert(TaskQueue *queue, int list_index_);
void Remove(TaskQueue *queue, int list_index_);
TaskQueue *Tail() { return &tail_; }
const TaskQueue *Tail() const { return &tail_; }
TaskQueue tail_ = { Type::Special, Priority::Normal };
TaskQueue *(lists_[kQueuesListsCount]);
};
class TaskQueue::TaskThreadPool {
struct Private {
};
public:
TaskThreadPool(const Private &) { }
static const std::shared_ptr<TaskThreadPool> &Instance();
void AddQueueTask(TaskQueue *queue, Task &&task);
void RemoveQueue(TaskQueue *queue);
~TaskThreadPool();
private:
void ThreadFunction();
std::vector<std::thread> threads_;
QMutex queues_mutex_;
// queues_mutex_ must be locked when working with the list.
TaskQueueList queue_list_;
QWaitCondition thread_condition_;
bool stopped_ = false;
int tasks_in_process_ = 0;
int background_tasks_in_process_ = 0;
};
TaskQueue::TaskQueueList::TaskQueueList() {
for (auto &list : lists_) {
list = &tail_;
}
}
void TaskQueue::TaskQueueList::Register(TaskQueue *queue) {
Assert(!queue->SerialTaskInProcess());
Insert(queue, kAllQueuesList);
if (queue->priority_ == Priority::Normal) {
Insert(queue, kOnlyNormalQueuesList);
}
}
void TaskQueue::TaskQueueList::Unregister(TaskQueue *queue) {
Remove(queue, kAllQueuesList);
if (queue->priority_ == Priority::Normal) {
Remove(queue, kOnlyNormalQueuesList);
}
}
void TaskQueue::TaskQueueList::Insert(TaskQueue *queue, int list_index_) {
Assert(list_index_ < kQueuesListsCount);
auto tail = Tail();
if (lists_[list_index_] == tail) {
lists_[list_index_] = queue;
}
auto &list_entry = queue->list_entries_[list_index_];
Assert(list_entry.after == nullptr);
if ((list_entry.before = tail->list_entries_[list_index_].before)) {
list_entry.before->list_entries_[list_index_].after = queue;
}
list_entry.after = tail;
tail->list_entries_[list_index_].before = queue;
}
void TaskQueue::TaskQueueList::Remove(TaskQueue *queue, int list_index_) {
Assert(list_index_ < kQueuesListsCount);
auto &list_entry = queue->list_entries_[list_index_];
Assert(list_entry.after != nullptr);
if (lists_[list_index_] == queue) {
lists_[list_index_] = list_entry.after;
} else {
Assert(list_entry.before != nullptr);
list_entry.before->list_entries_[list_index_].after = list_entry.after;
}
list_entry.after->list_entries_[list_index_].before = list_entry.before;
list_entry.before = list_entry.after = nullptr;
}
bool TaskQueue::TaskQueueList::IsInList(TaskQueue *queue) const {
if (queue->list_entries_[kAllQueuesList].after) {
return true;
}
Assert(queue->list_entries_[kOnlyNormalQueuesList].after == nullptr);
return false;
}
void TaskQueue::TaskQueueList::Clear() {
auto tail = Tail();
for (int i = 0; i < kQueuesListsCount; ++i) {
for (auto j = lists_[i], next = j; j != tail; j = next) {
auto &list_entry = j->list_entries_[i];
next = list_entry.after;
list_entry.before = list_entry.after = nullptr;
}
lists_[i] = tail;
}
}
bool TaskQueue::TaskQueueList::Empty(int list_index_) const {
Assert(list_index_ < kQueuesListsCount);
auto list = lists_[list_index_];
Assert(list != nullptr);
return (list->list_entries_[list_index_].after == nullptr);
}
TaskQueue *TaskQueue::TaskQueueList::TakeFirst(int list_index_) {
Assert(!Empty(list_index_));
auto queue = lists_[list_index_];
Unregister(queue);
// log_msgs.push_back("Unregistered from list in TakeFirst");
return queue;
}
void TaskQueue::TaskThreadPool::AddQueueTask(TaskQueue *queue, Task &&task) {
QMutexLocker lock(&queues_mutex_);
queue->tasks_.push_back(std::move(task));
auto list_was_empty = queue_list_.Empty(kAllQueuesList);
auto threads_count = threads_.size();
auto all_threads_processing = (threads_count == tasks_in_process_);
auto some_threads_are_vacant = !all_threads_processing && list_was_empty;
auto will_create_thread = !some_threads_are_vacant && (threads_count < MaxThreadsCount);
if (!queue->SerialTaskInProcess()) {
if (!queue_list_.IsInList(queue)) {
queue_list_.Register(queue);
}
}
if (will_create_thread) {
threads_.emplace_back([this]() {
ThreadFunction();
});
} else if (some_threads_are_vacant) {
Assert(threads_count > tasks_in_process_);
thread_condition_.wakeOne();
}
}
void TaskQueue::TaskThreadPool::RemoveQueue(TaskQueue *queue) {
QMutexLocker lock(&queues_mutex_);
if (queue_list_.IsInList(queue)) {
queue_list_.Unregister(queue);
}
if (queue->destroyed_flag_) {
*queue->destroyed_flag_ = true;
}
}
TaskQueue::TaskThreadPool::~TaskThreadPool() {
{
QMutexLocker lock(&queues_mutex_);
queue_list_.Clear();
stopped_ = true;
}
thread_condition_.wakeAll();
for (auto &thread : threads_) {
thread.join();
}
}
const std::shared_ptr<TaskQueue::TaskThreadPool> &TaskQueue::TaskThreadPool::Instance() { // static
static auto Pool = std::make_shared<TaskThreadPool>(Private());
return Pool;
}
void TaskQueue::TaskThreadPool::ThreadFunction() {
// Flag marking that the previous processed task was
// with a Background priority. We count all the background
// tasks being processed.
bool background_task = false;
// Saved serial queue pointer. When we process a serial
// queue task we don't return the queue to the list until
// the task is processed and we return it on the next cycle.
TaskQueue *serial_queue = nullptr;
bool serial_queue_destroyed = false;
bool task_was_processed = false;
while (true) {
Task task;
{
QMutexLocker lock(&queues_mutex_);
// Finish the previous task processing.
if (task_was_processed) {
--tasks_in_process_;
}
if (background_task) {
--background_tasks_in_process_;
background_task = false;
}
if (serial_queue) {
if (!serial_queue_destroyed) {
serial_queue->destroyed_flag_ = nullptr;
if (!serial_queue->tasks_.empty()) {
queue_list_.Register(serial_queue);
}
}
serial_queue = nullptr;
serial_queue_destroyed = false;
}
// Wait for a task to appear in the queues list.
while (queue_list_.Empty(kAllQueuesList)) {
if (stopped_) {
return;
}
thread_condition_.wait(&queues_mutex_);
}
// Select a task we will be processing.
auto processing_background = (background_tasks_in_process_ > 0);
auto take_only_normal = processing_background && !queue_list_.Empty(kOnlyNormalQueuesList);
auto take_from_list_ = take_only_normal ? kOnlyNormalQueuesList : kAllQueuesList;
auto queue = queue_list_.TakeFirst(take_from_list_);
Assert(!queue->tasks_.empty());
task = std::move(queue->tasks_.front());
queue->tasks_.pop_front();
if (queue->type_ == Type::Serial) {
// Serial queues are returned in the list for processing
// only after the task is finished.
serial_queue = queue;
Assert(serial_queue->destroyed_flag_ == nullptr);
serial_queue->destroyed_flag_ = &serial_queue_destroyed;
} else if (!queue->tasks_.empty()) {
queue_list_.Register(queue);
}
++tasks_in_process_;
task_was_processed = true;
if (queue->priority_ == Priority::Background) {
++background_tasks_in_process_;
background_task = true;
}
}
task();
}
}
TaskQueue::TaskQueue(Type type, Priority priority)
: type_(type)
, priority_(priority) {
if (type_ != Type::Main && type_ != Type::Special) {
weak_thread_pool_ = TaskThreadPool::Instance();
}
}
TaskQueue::~TaskQueue() {
if (type_ != Type::Main && type_ != Type::Special) {
if (auto thread_pool = weak_thread_pool_.lock()) {
thread_pool->RemoveQueue(this);
}
}
}
void TaskQueue::Put(Task &&task) {
if (type_ == Type::Main) {
QMutexLocker lock(&tasks_mutex_);
tasks_.push_back(std::move(task));
Sandbox::MainThreadTaskAdded();
} else {
Assert(type_ != Type::Special);
TaskThreadPool::Instance()->AddQueueTask(this, std::move(task));
}
}
void TaskQueue::ProcessMainTasks() { // static
Assert(std::this_thread::get_id() == MainThreadId);
while (ProcessOneMainTask()) {
}
}
void TaskQueue::ProcessMainTasks(TimeMs max_time_spent) { // static
Assert(std::this_thread::get_id() == MainThreadId);
auto start_time = getms();
while (ProcessOneMainTask()) {
if (getms() >= start_time + max_time_spent) {
break;
}
}
}
bool TaskQueue::ProcessOneMainTask() { // static
Task task;
{
QMutexLocker lock(&Main().tasks_mutex_);
auto &tasks = Main().tasks_;
if (tasks.empty()) {
return false;
}
task = std::move(tasks.front());
tasks.pop_front();
}
task();
return true;
}
bool TaskQueue::IsMyThread() const {
if (type_ == Type::Main) {
return (std::this_thread::get_id() == MainThreadId);
}
Assert(type_ != Type::Special);
return false;
}
// Default queues.
TaskQueue &TaskQueue::Main() { // static
static TaskQueue MainQueue { Type::Main, Priority::Normal };
return MainQueue;
}
TaskQueue &TaskQueue::Normal() { // static
static TaskQueue NormalQueue { Type::Concurrent, Priority::Normal };
return NormalQueue;
}
TaskQueue &TaskQueue::Background() { // static
static TaskQueue BackgroundQueue { Type::Concurrent, Priority::Background };
return BackgroundQueue;
}
} // namespace base

View File

@@ -1,102 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include <memory>
namespace base {
using Task = lambda_once<void()>;
// An attempt to create/use a TaskQueue or one of the default queues
// after the main() has returned leads to an undefined behaviour.
class TaskQueue {
enum class Type {
Main, // Unique queue for main thread tasks.
Serial,
Concurrent,
Special, // Unique special queue for thread pool lists terminal item.
};
public:
enum class Priority {
Normal,
Background,
};
// Creating custom serial queues.
TaskQueue(Priority priority) : TaskQueue(Type::Serial, priority) {
}
// Default main and two concurrent queues.
static TaskQueue &Main();
static TaskQueue &Normal();
static TaskQueue &Background();
void Put(Task &&task);
static void ProcessMainTasks();
static void ProcessMainTasks(TimeMs max_time_spent);
~TaskQueue();
private:
static bool ProcessOneMainTask();
TaskQueue(Type type, Priority priority);
bool IsMyThread() const;
bool SerialTaskInProcess() const {
return (destroyed_flag_ != nullptr);
}
const Type type_;
const Priority priority_;
std::deque<Task> tasks_;
QMutex tasks_mutex_; // Only for the main queue.
// Only for the other queues, not main.
class TaskThreadPool;
std::weak_ptr<TaskThreadPool> weak_thread_pool_;
class TaskQueueList;
struct TaskQueueListEntry {
TaskQueue *before = nullptr;
TaskQueue *after = nullptr;
};
// Thread pool queues linked list.
static constexpr int kAllQueuesList = 0;
// Thread pool queues linked list with excluded Background queues.
static constexpr int kOnlyNormalQueuesList = 1;
static constexpr int kQueuesListsCount = 2;
TaskQueueListEntry list_entries_[kQueuesListsCount];
// Only for Serial queues: non-null value means a task is currently processed.
bool *destroyed_flag_ = nullptr;
};
} // namespace base

View File

@@ -280,6 +280,55 @@ weak_ptr<T> make_weak(const std::weak_ptr<T> &value) {
} // namespace base
namespace crl {
template <typename T, typename Enable>
struct guard_traits;
template <typename T>
struct guard_traits<base::weak_ptr<T>, void> {
static base::weak_ptr<T> create(const base::weak_ptr<T> &value) {
return value;
}
static base::weak_ptr<T> create(base::weak_ptr<T> &&value) {
return std::move(value);
}
static bool check(const base::weak_ptr<T> &guard) {
return guard.get() != nullptr;
}
};
template <typename T>
struct guard_traits<
T*,
std::enable_if_t<
std::is_base_of_v<base::has_weak_ptr, std::remove_cv_t<T>>>> {
static base::weak_ptr<T> create(T *value) {
return value;
}
static bool check(const base::weak_ptr<T> &guard) {
return guard.get() != nullptr;
}
};
template <typename T>
struct guard_traits<
gsl::not_null<T*>,
std::enable_if_t<
std::is_base_of_v<base::has_weak_ptr, std::remove_cv_t<T>>>> {
static base::weak_ptr<T> create(gsl::not_null<T*> value) {
return value.get();
}
static bool check(const base::weak_ptr<T> &guard) {
return guard.get() != nullptr;
}
};
} // namespace crl
#ifdef QT_VERSION
template <typename Lambda>
inline void InvokeQueued(const base::has_weak_ptr *context, Lambda &&lambda) {

View File

@@ -30,6 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/labels.h"
#include "styles/style_boxes.h"
#include "platform/platform_file_utilities.h"
#include "core/click_handler_types.h"
AboutBox::AboutBox(QWidget *parent)
: _version(this, lng_about_version(lt_version, QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString())), st::aboutVersionLink)
@@ -43,7 +44,18 @@ void AboutBox::prepare() {
addButton(langFactory(lng_close), [this] { closeBox(); });
const auto linkHook = [](const ClickHandlerPtr &link, auto button) {
if (const auto url = dynamic_cast<UrlClickHandler*>(link.get())) {
url->UrlClickHandler::onClick(button);
return false;
}
return true;
};
_text3->setRichText(lng_about_text_3(lt_faq_open, qsl("[a href=\"%1\"]").arg(telegramFaqLink()), lt_faq_close, qsl("[/a]")));
_text1->setClickHandlerHook(linkHook);
_text2->setClickHandlerHook(linkHook);
_text3->setClickHandlerHook(linkHook);
_version->setClickedCallback([this] { showVersionHistory(); });

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

@@ -715,10 +715,10 @@ public:
}
void peerListSortRows(
base::lambda<bool(const PeerListRow &a, const PeerListRow &b)> compare) override {
_content->reorderRows([compare = std::move(compare)](
_content->reorderRows([&](
auto &&begin,
auto &&end) {
std::sort(begin, end, [&compare](auto &&a, auto &&b) {
std::sort(begin, end, [&](auto &&a, auto &&b) {
return compare(*a, *b);
});
});
@@ -726,10 +726,10 @@ public:
int peerListPartitionRows(
base::lambda<bool(const PeerListRow &a)> border) override {
auto result = 0;
_content->reorderRows([border = std::move(border), &result](
_content->reorderRows([&](
auto &&begin,
auto &&end) {
auto edge = std::stable_partition(begin, end, [&border](
auto edge = std::stable_partition(begin, end, [&](
auto &&current) {
return border(*current);
});

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()) {
@@ -1551,13 +1551,13 @@ int StickersBox::Inner::fillSetCount(const Stickers::Set &set) const {
auto customIt = Auth().data().stickerSets().constFind(Stickers::CustomSetId);
if (customIt != Auth().data().stickerSets().cend()) {
added = customIt->stickers.size();
for_const (auto &sticker, cGetRecentStickers()) {
for_const (auto &sticker, Stickers::GetRecentPack()) {
if (customIt->stickers.indexOf(sticker.first) < 0) {
++added;
}
}
} else {
added = cGetRecentStickers().size();
added = Stickers::GetRecentPack().size();
}
}
return result + added;

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

@@ -37,7 +37,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "apiwrap.h"
#include "observer_peer.h"
#include "platform/platform_specific.h"
#include "base/task_queue.h"
#include "window/main_window.h"
namespace Calls {
@@ -365,10 +364,12 @@ void Panel::initLayout() {
initGeometry();
Notify::PeerUpdateValue(_user, Notify::PeerUpdate::Flag::PhotoChanged)
| rpl::start_with_next(
[this] { processUserPhoto(); },
lifetime());
Notify::PeerUpdateValue(
_user,
Notify::PeerUpdate::Flag::PhotoChanged
) | rpl::start_with_next(
[this] { processUserPhoto(); },
lifetime());
subscribe(Auth().downloaderTaskFinished(), [this] {
refreshUserPhoto();
});
@@ -386,10 +387,15 @@ void Panel::toggleOpacityAnimation(bool visible) {
if (_useTransparency) {
if (_animationCache.isNull()) {
showControls();
_animationCache = myGrab(this);
_animationCache = Ui::GrabWidget(this);
hideChildren();
}
_opacityAnimation.start([this] { update(); }, _visible ? 0. : 1., _visible ? 1. : 0., st::callPanelDuration, _visible ? anim::easeOutCirc : anim::easeInCirc);
_opacityAnimation.start(
[this] { update(); },
_visible ? 0. : 1.,
_visible ? 1. : 0.,
st::callPanelDuration,
_visible ? anim::easeOutCirc : anim::easeInCirc);
}
if (isHidden() && _visible) {
show();
@@ -418,10 +424,8 @@ void Panel::showControls() {
void Panel::destroyDelayed() {
hide();
base::TaskQueue::Main().Put([weak = QPointer<Panel>(this)] {
if (weak) {
delete weak.data();
}
crl::on_main(this, [=] {
delete this;
});
}
@@ -490,7 +494,7 @@ void Panel::createUserpicCache(ImagePtr image) {
_user->name
).paintSquare(p, 0, 0, st::callWidth, st::callWidth);
}
Images::prepareRound(filled, ImageRoundRadius::Large, ImageRoundCorner::TopLeft | ImageRoundCorner::TopRight);
Images::prepareRound(filled, ImageRoundRadius::Large, RectPart::TopLeft | RectPart::TopRight);
_userPhoto = App::pixmapFromImageInPlace(std::move(filled));
}
refreshCacheImageUserPhoto();

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

@@ -347,7 +347,7 @@ void SetsReceived(const QVector<MTPStickerSet> &data, int32 hash) {
}
}
auto writeRecent = false;
auto &recent = cGetRecentStickers();
auto &recent = GetRecentPack();
for (auto it = sets.begin(), e = sets.end(); it != e;) {
bool installed = (it->flags & MTPDstickerSet::Flag::f_installed);
bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured);
@@ -449,7 +449,7 @@ void SpecialSetReceived(uint64 setId, const QString &setTitle, const QVector<MTP
}
auto writeRecent = false;
auto &recent = cGetRecentStickers();
auto &recent = GetRecentPack();
for (auto i = recent.begin(); i != recent.cend();) {
if (it->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
i = recent.erase(i);
@@ -748,7 +748,7 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data) {
}
auto writeRecent = false;
auto &recent = cGetRecentStickers();
auto &recent = GetRecentPack();
for (auto i = recent.begin(); i != recent.cend();) {
if (set->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
i = recent.erase(i);
@@ -816,4 +816,56 @@ QString GetSetTitle(const MTPDstickerSet &s) {
return title;
}
RecentStickerPack &GetRecentPack() {
if (cRecentStickers().isEmpty() && !cRecentStickersPreload().isEmpty()) {
const auto p = cRecentStickersPreload();
cSetRecentStickersPreload(RecentStickerPreload());
auto &recent = cRefRecentStickers();
recent.reserve(p.size());
for (const auto &preloaded : p) {
const auto document = App::document(preloaded.first);
if (!document || !document->sticker()) continue;
recent.push_back(qMakePair(document, preloaded.second));
}
}
return cRefRecentStickers();
}
void IncrementRecentHashtag(RecentHashtagPack &recent, const QString &tag) {
auto i = recent.begin(), e = recent.end();
for (; i != e; ++i) {
if (i->first == tag) {
++i->second;
if (qAbs(i->second) > 0x4000) {
for (auto j = recent.begin(); j != e; ++j) {
if (j->second > 1) {
j->second /= 2;
} else if (j->second > 0) {
j->second = 1;
}
}
}
for (; i != recent.begin(); --i) {
if (qAbs((i - 1)->second) > qAbs(i->second)) {
break;
}
qSwap(*i, *(i - 1));
}
break;
}
}
if (i == e) {
while (recent.size() >= 64) recent.pop_back();
recent.push_back(qMakePair(tag, 1));
for (i = recent.end() - 1; i != recent.begin(); --i) {
if ((i - 1)->second > i->second) {
break;
}
qSwap(*i, *(i - 1));
}
}
}
} // namespace Stickers

View File

@@ -85,4 +85,8 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data);
QString GetSetTitle(const MTPDstickerSet &s);
RecentStickerPack &GetRecentPack();
void IncrementRecentHashtag(RecentHashtagPack &recent, const QString &tag);
} // namespace Stickers

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,14 +1104,16 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
}
void StickersListWidget::removeRecentSticker(int section, int index) {
if (_section != Section::Stickers || section >= _mySets.size() || _mySets[section].id != Stickers::RecentSetId) {
if ((_section != Section::Stickers)
|| (section >= int(_mySets.size()))
|| (_mySets[section].id != Stickers::RecentSetId)) {
return;
}
clearSelection();
bool refresh = false;
auto sticker = _mySets[section].pack[index];
auto &recent = cGetRecentStickers();
auto &recent = Stickers::GetRecentPack();
for (int32 i = 0, l = recent.size(); i < l; ++i) {
if (recent.at(i).first == sticker) {
recent.removeAt(i);
@@ -1122,7 +1145,9 @@ void StickersListWidget::removeRecentSticker(int section, int index) {
}
void StickersListWidget::removeFavedSticker(int section, int index) {
if (_section != Section::Stickers || section >= _mySets.size() || _mySets[section].id != Stickers::FavedSetId) {
if ((_section != Section::Stickers)
|| (section >= int(_mySets.size()))
|| (_mySets[section].id != Stickers::FavedSetId)) {
return;
}
@@ -1229,8 +1254,7 @@ void StickersListWidget::refreshStickers() {
}
void StickersListWidget::refreshSettingsVisibility() {
auto visible = (_section == Section::Stickers)
&& _mySets.isEmpty();
const auto visible = (_section == Section::Stickers) && _mySets.empty();
_settings->setVisible(visible);
}
@@ -1251,7 +1275,7 @@ void StickersListWidget::preloadImages() {
for (int j = 0; j != count; ++j) {
if (++k > _columnCount * (_columnCount + 1)) break;
auto sticker = sets.at(i).pack.at(j);
auto sticker = sets[i].pack.at(j);
if (!sticker || !sticker->sticker()) continue;
bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128));
@@ -1272,10 +1296,15 @@ uint64 StickersListWidget::currentSet(int yOffset) const {
if (_section == Section::Featured) {
return Stickers::FeaturedSetId;
}
return _mySets.isEmpty() ? Stickers::RecentSetId : _mySets[sectionInfoByOffset(yOffset).section].id;
return _mySets.empty()
? Stickers::RecentSetId
: _mySets[sectionInfoByOffset(yOffset).section].id;
}
void StickersListWidget::appendSet(Sets &to, uint64 setId, AppendSkip skip) {
void StickersListWidget::appendSet(
std::vector<Set> &to,
uint64 setId,
AppendSkip skip) {
auto &sets = Auth().data().stickerSets();
auto it = sets.constFind(setId);
if (it == sets.cend() || it->stickers.isEmpty()) return;
@@ -1302,7 +1331,7 @@ void StickersListWidget::refreshRecentStickers(bool performResize) {
_custom.clear();
clearSelection();
auto &sets = Auth().data().stickerSets();
auto &recent = cGetRecentStickers();
auto &recent = Stickers::GetRecentPack();
auto customIt = sets.constFind(Stickers::CustomSetId);
auto cloudIt = sets.constFind(Stickers::CloudRecentSetId);
@@ -1423,7 +1452,7 @@ void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) {
void StickersListWidget::fillIcons(QList<StickerIcon> &icons) {
icons.clear();
icons.reserve(_mySets.size() + 1);
if (Auth().data().featuredStickerSetsUnreadCount() && !_featuredSets.isEmpty()) {
if (Auth().data().featuredStickerSetsUnreadCount() && !_featuredSets.empty()) {
icons.push_back(StickerIcon(Stickers::FeaturedSetId));
}
@@ -1457,7 +1486,8 @@ void StickersListWidget::fillIcons(QList<StickerIcon> &icons) {
icons.push_back(StickerIcon(_mySets[i].id, s, pixw, pixh));
}
if (!Auth().data().featuredStickerSetsUnreadCount() && !_featuredSets.isEmpty()) {
if (!Auth().data().featuredStickerSetsUnreadCount()
&& !_featuredSets.empty()) {
icons.push_back(StickerIcon(Stickers::FeaturedSetId));
}
}
@@ -1773,7 +1803,7 @@ void StickersListWidget::removeSet(uint64 setId) {
request(MTPmessages_UninstallStickerSet(MTP_inputStickerSetShortName(MTP_string(it->shortName)))).send();
}
auto writeRecent = false;
auto &recent = cGetRecentStickers();
auto &recent = Stickers::GetRecentPack();
for (auto i = recent.begin(); i != recent.cend();) {
if (it->stickers.indexOf(i->first) >= 0) {
i = recent.erase(i);

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,197 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "core/changelogs.h"
#include "storage/localstorage.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "apiwrap.h"
namespace Core {
namespace {
std::map<int, const char*> AlphaLogs() {
return {
{
1001024,
"\xE2\x80\x94 Radically improved navigation. "
"New side panel on the right with quick access to "
"shared media and group members.\n"
"\xE2\x80\x94 Pinned Messages. If you are a channel admin, "
"pin messages to focus your subscribers\xE2\x80\x99 attention "
"on important announcements.\n"
"\xE2\x80\x94 Also supported clearing history in supergroups "
"and added a host of minor improvements."
},
{
1001026,
"\xE2\x80\x94 Admin badges in supergroup messages.\n"
"\xE2\x80\x94 Fix crashing on launch in OS X 10.6.\n"
"\xE2\x80\x94 Bug fixes and other minor improvements."
},
{
1001027,
"\xE2\x80\x94 Saved Messages. Bookmark messages by forwarding them "
"to \xE2\x80\x9C""Saved Messages\xE2\x80\x9D. "
"Access them from the Chats list or from the side menu."
},
{
1002002,
"\xE2\x80\x94 Grouped photos and videos are displayed as albums."
},
{
1002004,
"\xE2\x80\x94 Group media into an album "
"when sharing multiple photos and videos.\n"
"\xE2\x80\x94 Bug fixes and other minor improvements."
},
{
1002005,
"\xE2\x80\x94 When viewing a photo from an album, "
"you'll see other pictures from the same group "
"as thumbnails in the lower part of the screen.\n"
"\xE2\x80\x94 When composing an album paste "
"additional media from the clipboard.\n"
"\xE2\x80\x94 Bug fixes and other minor improvements."
},
{
1002007,
"\xE2\x80\x94 Use fast reply button in group chats.\n"
"\xE2\x80\x94 Select a message you want to reply to by "
"pressing Ctrl+Up and Ctrl+Down."
},
};
}
QString FormatVersionDisplay(int version) {
return QString::number(version / 1000000)
+ '.' + QString::number((version % 1000000) / 1000)
+ ((version % 1000)
? ('.' + QString::number(version % 1000))
: QString());
}
QString FormatVersionPrecise(int version) {
return QString::number(version / 1000000)
+ '.' + QString::number((version % 1000000) / 1000)
+ '.' + QString::number(version % 1000);
}
} // namespace
Changelogs::Changelogs(not_null<AuthSession*> session, int oldVersion)
: _session(session)
, _oldVersion(oldVersion) {
_chatsSubscription = subscribe(
_session->data().moreChatsLoaded(),
[this] { requestCloudLogs(); });
}
std::unique_ptr<Changelogs> Changelogs::Create(
not_null<AuthSession*> session) {
const auto oldVersion = Local::oldMapVersion();
return (oldVersion > 0 && oldVersion < AppVersion)
? std::make_unique<Changelogs>(session, oldVersion)
: nullptr;
}
void Changelogs::requestCloudLogs() {
unsubscribe(base::take(_chatsSubscription));
const auto callback = [this](const MTPUpdates &result) {
_session->api().applyUpdates(result);
auto resultEmpty = true;
switch (result.type()) {
case mtpc_updateShortMessage:
case mtpc_updateShortChatMessage:
case mtpc_updateShort:
resultEmpty = false;
break;
case mtpc_updatesCombined:
resultEmpty = result.c_updatesCombined().vupdates.v.isEmpty();
break;
case mtpc_updates:
resultEmpty = result.c_updates().vupdates.v.isEmpty();
break;
case mtpc_updatesTooLong:
case mtpc_updateShortSentMessage:
LOG(("API Error: Bad updates type in app changelog."));
break;
}
if (resultEmpty) {
addLocalLogs();
}
};
_session->api().requestChangelog(
FormatVersionPrecise(_oldVersion),
base::lambda_guarded(this, callback));
}
void Changelogs::addLocalLogs() {
if (cAlphaVersion() || cBetaVersion()) {
addAlphaLogs();
}
if (!_addedSomeLocal) {
const auto text = lng_new_version_wrap(
lt_version,
str_const_toString(AppVersionStr),
lt_changes,
lang(lng_new_version_minor),
lt_link,
qsl("https://desktop.telegram.org/changelog"));
addLocalLog(text.trimmed());
}
}
void Changelogs::addLocalLog(const QString &text) {
auto textWithEntities = TextWithEntities{ text };
TextUtilities::ParseEntities(textWithEntities, TextParseLinks);
App::wnd()->serviceNotification(
textWithEntities,
MTP_messageMediaEmpty(),
unixtime());
_addedSomeLocal = true;
};
void Changelogs::addAlphaLogs() {
for (const auto[version, changes] : AlphaLogs()) {
addAlphaLog(version, changes);
}
}
void Changelogs::addAlphaLog(int changeVersion, const char *changes) {
if (_oldVersion >= changeVersion) {
return;
}
const auto version = FormatVersionDisplay(changeVersion);
const auto text = qsl("New in version %1:\n\n").arg(version)
+ QString::fromUtf8(changes).trimmed();
addLocalLog(text);
}
} // namespace Core

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

@@ -143,7 +143,7 @@ void HiddenUrlClickHandler::doOpen(QString url) {
Ui::show(Box<ConfirmBox>(lang(lng_open_this_link) + qsl("\n\n") + displayUrl, lang(lng_open_link), [urlText] {
Ui::hideLayer();
UrlClickHandler::doOpen(urlText);
}));
}), LayerOption::KeepOther);
} else {
UrlClickHandler::doOpen(urlText);
}

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,226 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
class PreLaunchWindow : public QWidget {
public:
PreLaunchWindow(QString title = QString());
void activate();
int basicSize() const {
return _size;
}
~PreLaunchWindow();
static PreLaunchWindow *instance();
protected:
int _size;
};
class PreLaunchLabel : public QLabel {
public:
PreLaunchLabel(QWidget *parent);
void setText(const QString &text);
};
class PreLaunchInput : public QLineEdit {
public:
PreLaunchInput(QWidget *parent, bool password = false);
};
class PreLaunchLog : public QTextEdit {
public:
PreLaunchLog(QWidget *parent);
};
class PreLaunchButton : public QPushButton {
public:
PreLaunchButton(QWidget *parent, bool confirm = true);
void setText(const QString &text);
};
class PreLaunchCheckbox : public QCheckBox {
public:
PreLaunchCheckbox(QWidget *parent);
void setText(const QString &text);
};
class NotStartedWindow : public PreLaunchWindow {
public:
NotStartedWindow();
protected:
void closeEvent(QCloseEvent *e);
void resizeEvent(QResizeEvent *e);
private:
void updateControls();
PreLaunchLabel _label;
PreLaunchLog _log;
PreLaunchButton _close;
};
class LastCrashedWindow : public PreLaunchWindow {
Q_OBJECT
public:
LastCrashedWindow();
public slots:
void onViewReport();
void onSaveReport();
void onSendReport();
void onGetApp();
void onNetworkSettings();
void onNetworkSettingsSaved(QString host, quint32 port, QString username, QString password);
void onContinue();
void onCheckingFinished();
void onSendingError(QNetworkReply::NetworkError e);
void onSendingFinished();
void onSendingProgress(qint64 uploaded, qint64 total);
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
void onUpdateRetry();
void onUpdateSkip();
void onUpdateChecking();
void onUpdateLatest();
void onUpdateDownloading(qint64 ready, qint64 total);
void onUpdateReady();
void onUpdateFailed();
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
protected:
void closeEvent(QCloseEvent *e);
void resizeEvent(QResizeEvent *e);
private:
QString minidumpFileName();
void updateControls();
QString _host, _username, _password;
quint32 _port;
PreLaunchLabel _label, _pleaseSendReport, _yourReportName, _minidump;
PreLaunchLog _report;
PreLaunchButton _send, _sendSkip, _networkSettings, _continue, _showReport, _saveReport, _getApp;
PreLaunchCheckbox _includeUsername;
QString _minidumpName, _minidumpFull, _reportText;
QString _reportUsername, _reportTextNoUsername;
QByteArray getCrashReportRaw() const;
bool _reportShown, _reportSaved;
void excludeReportUsername();
enum SendingState {
SendingNoReport,
SendingUpdateCheck,
SendingNone,
SendingTooOld,
SendingTooMany,
SendingUnofficial,
SendingProgress,
SendingUploading,
SendingFail,
SendingDone,
};
SendingState _sendingState;
PreLaunchLabel _updating;
qint64 _sendingProgress, _sendingTotal;
QNetworkAccessManager _sendManager;
QNetworkReply *_checkReply, *_sendReply;
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
PreLaunchButton _updatingCheck, _updatingSkip;
enum UpdatingState {
UpdatingNone,
UpdatingCheck,
UpdatingLatest,
UpdatingDownload,
UpdatingFail,
UpdatingReady
};
UpdatingState _updatingState;
QString _newVersionDownload;
void setUpdatingState(UpdatingState state, bool force = false);
void setDownloadProgress(qint64 ready, qint64 total);
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
QString getReportField(const QLatin1String &name, const QLatin1String &prefix);
void addReportFieldPart(const QLatin1String &name, const QLatin1String &prefix, QHttpMultiPart *multipart);
};
class NetworkSettingsWindow : public PreLaunchWindow {
Q_OBJECT
public:
NetworkSettingsWindow(QWidget *parent, QString host, quint32 port, QString username, QString password);
signals:
void saved(QString host, quint32 port, QString username, QString password);
public slots:
void onSave();
protected:
void closeEvent(QCloseEvent *e);
void resizeEvent(QResizeEvent *e);
private:
void updateControls();
PreLaunchLabel _hostLabel, _portLabel, _usernameLabel, _passwordLabel;
PreLaunchInput _hostInput, _portInput, _usernameInput, _passwordInput;
PreLaunchButton _save, _cancel;
QWidget *_parent;
};
class ShowCrashReportWindow : public PreLaunchWindow {
public:
ShowCrashReportWindow(const QString &text);
protected:
void resizeEvent(QResizeEvent *e);
void closeEvent(QCloseEvent *e);
private:
PreLaunchLog _log;
};

View File

@@ -0,0 +1,580 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "core/crash_reports.h"
#include "platform/platform_specific.h"
#include <signal.h>
#include <new>
#include <mutex>
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
// see https://blog.inventic.eu/2012/08/qt-and-google-breakpad/
#ifdef Q_OS_WIN
#pragma warning(push)
#pragma warning(disable:4091)
#include "client/windows/handler/exception_handler.h"
#pragma warning(pop)
#elif defined Q_OS_MAC // Q_OS_WIN
#include <execinfo.h>
#include <signal.h>
#include <sys/syscall.h>
#include <dlfcn.h>
#include <unistd.h>
#ifdef MAC_USE_BREAKPAD
#include "client/mac/handler/exception_handler.h"
#else // MAC_USE_BREAKPAD
#include "client/crashpad_client.h"
#endif // else for MAC_USE_BREAKPAD
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC
#include <execinfo.h>
#include <signal.h>
#include <sys/syscall.h>
#include "client/linux/handler/exception_handler.h"
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
namespace CrashReports {
namespace {
using Annotations = std::map<std::string, std::string>;
using AnnotationRefs = std::map<std::string, const QString*>;
Annotations ProcessAnnotations;
AnnotationRefs ProcessAnnotationRefs;
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
QString ReportPath;
FILE *ReportFile = nullptr;
int ReportFileNo = 0;
char LaunchedDateTimeStr[32] = { 0 };
char LaunchedBinaryName[256] = { 0 };
void SafeWriteChar(char ch) {
fwrite(&ch, 1, 1, ReportFile);
}
template <bool Unsigned, typename Type>
struct writeNumberSignAndRemoveIt {
static void call(Type &number) {
if (number < 0) {
SafeWriteChar('-');
number = -number;
}
}
};
template <typename Type>
struct writeNumberSignAndRemoveIt<true, Type> {
static void call(Type &number) {
}
};
template <typename Type>
const dump &SafeWriteNumber(const dump &stream, Type number) {
if (!ReportFile) return stream;
writeNumberSignAndRemoveIt<(Type(-1) > Type(0)), Type>::call(number);
Type upper = 1, prev = number / 10;
while (prev >= upper) {
upper *= 10;
}
while (upper > 0) {
int digit = (number / upper);
SafeWriteChar('0' + digit);
number -= digit * upper;
upper /= 10;
}
return stream;
}
using ReservedMemoryChunk = std::array<gsl::byte, 1024 * 1024>;
std::unique_ptr<ReservedMemoryChunk> ReservedMemory;
void InstallOperatorNewHandler() {
ReservedMemory = std::make_unique<ReservedMemoryChunk>();
std::set_new_handler([] {
std::set_new_handler(nullptr);
ReservedMemory.reset();
Unexpected("Could not allocate!");
});
}
Qt::HANDLE ReportingThreadId = nullptr;
bool ReportingHeaderWritten = false;
QMutex ReportingMutex;
const char *BreakpadDumpPath = nullptr;
const wchar_t *BreakpadDumpPathW = nullptr;
#if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64
struct sigaction SIG_def[32];
void SignalHandler(int signum, siginfo_t *info, void *ucontext) {
if (signum > 0) {
sigaction(signum, &SIG_def[signum], 0);
}
#else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
void SignalHandler(int signum) {
#endif // else for Q_OS_MAC || Q_OS_LINUX || Q_OS_LINUX64
const char* name = 0;
switch (signum) {
case SIGABRT: name = "SIGABRT"; break;
case SIGSEGV: name = "SIGSEGV"; break;
case SIGILL: name = "SIGILL"; break;
case SIGFPE: name = "SIGFPE"; break;
#ifndef Q_OS_WIN
case SIGBUS: name = "SIGBUS"; break;
case SIGSYS: name = "SIGSYS"; break;
#endif // !Q_OS_WIN
}
Qt::HANDLE thread = QThread::currentThreadId();
if (thread == ReportingThreadId) return;
QMutexLocker lock(&ReportingMutex);
ReportingThreadId = thread;
if (!ReportingHeaderWritten) {
ReportingHeaderWritten = true;
auto dec2hex = [](int value) -> char {
if (value >= 0 && value < 10) {
return '0' + value;
} else if (value >= 10 && value < 16) {
return 'a' + (value - 10);
}
return '#';
};
for (const auto &i : ProcessAnnotationRefs) {
QByteArray utf8 = i.second->toUtf8();
std::string wrapped;
wrapped.reserve(4 * utf8.size());
for (auto ch : utf8) {
auto uch = static_cast<uchar>(ch);
wrapped.append("\\x", 2).append(1, dec2hex(uch >> 4)).append(1, dec2hex(uch & 0x0F));
}
ProcessAnnotations[i.first] = wrapped;
}
const Annotations c_ProcessAnnotations(ProcessAnnotations);
for (const auto &i : c_ProcessAnnotations) {
dump() << i.first.c_str() << ": " << i.second.c_str() << "\n";
}
psWriteDump();
dump() << "\n";
}
if (name) {
dump() << "Caught signal " << signum << " (" << name << ") in thread " << uint64(thread) << "\n";
} else if (signum == -1) {
dump() << "Google Breakpad caught a crash, minidump written in thread " << uint64(thread) << "\n";
if (BreakpadDumpPath) {
dump() << "Minidump: " << BreakpadDumpPath << "\n";
} else if (BreakpadDumpPathW) {
dump() << "Minidump: " << BreakpadDumpPathW << "\n";
}
} else {
dump() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n";
}
// see https://github.com/benbjohnson/bandicoot
#if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64
ucontext_t *uc = (ucontext_t*)ucontext;
void *caller = 0;
if (uc) {
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
/* OSX < 10.6 */
#if defined(__x86_64__)
caller = (void*)uc->uc_mcontext->__ss.__rip;
#elif defined(__i386__)
caller = (void*)uc->uc_mcontext->__ss.__eip;
#else
caller = (void*)uc->uc_mcontext->__ss.__srr0;
#endif
#elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
/* OSX >= 10.6 */
#if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
caller = (void*)uc->uc_mcontext->__ss.__rip;
#else
caller = (void*)uc->uc_mcontext->__ss.__eip;
#endif
#elif defined(__linux__)
/* Linux */
#if defined(__i386__)
caller = (void*)uc->uc_mcontext.gregs[14]; /* Linux 32 */
#elif defined(__X86_64__) || defined(__x86_64__)
caller = (void*)uc->uc_mcontext.gregs[16]; /* Linux 64 */
#elif defined(__ia64__) /* Linux IA64 */
caller = (void*)uc->uc_mcontext.sc_ip;
#endif
#endif
}
void *addresses[132] = { 0 };
size_t size = backtrace(addresses, 128);
/* overwrite sigaction with caller's address */
if (caller) {
for (int i = size; i > 1; --i) {
addresses[i + 3] = addresses[i];
}
addresses[2] = (void*)0x1;
addresses[3] = caller;
addresses[4] = (void*)0x1;
}
#ifdef Q_OS_MAC
dump() << "\nBase image addresses:\n";
for (size_t i = 0; i < size; ++i) {
Dl_info info;
dump() << i << " ";
if (dladdr(addresses[i], &info)) {
dump() << uint64(info.dli_fbase) << " (" << info.dli_fname << ")\n";
} else {
dump() << "_unknown_module_\n";
}
}
#endif // Q_OS_MAC
dump() << "\nBacktrace:\n";
backtrace_symbols_fd(addresses, size, ReportFileNo);
#else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
dump() << "\nBacktrace:\n";
psWriteStackTrace();
#endif // else for Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
dump() << "\n";
ReportingThreadId = nullptr;
}
bool SetSignalHandlers = true;
bool CrashLogged = false;
#if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
google_breakpad::ExceptionHandler* BreakpadExceptionHandler = 0;
#ifdef Q_OS_WIN
bool DumpCallback(const wchar_t* _dump_dir, const wchar_t* _minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool success)
#elif defined Q_OS_MAC // Q_OS_WIN
bool DumpCallback(const char* _dump_dir, const char* _minidump_id, void *context, bool success)
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC
bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success)
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
{
if (CrashLogged) return success;
CrashLogged = true;
#ifdef Q_OS_WIN
BreakpadDumpPathW = _minidump_id;
SignalHandler(-1);
#else // Q_OS_WIN
#ifdef Q_OS_MAC
BreakpadDumpPath = _minidump_id;
#else // Q_OS_MAC
BreakpadDumpPath = md.path();
#endif // else for Q_OS_MAC
SignalHandler(-1, 0, 0);
#endif // else for Q_OS_WIN
return success;
}
#endif // !Q_OS_MAC || MAC_USE_BREAKPAD
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
} // namespace
void StartCatching() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
ProcessAnnotations["Binary"] = cExeName().toUtf8().constData();
ProcessAnnotations["ApiId"] = QString::number(ApiId).toUtf8().constData();
ProcessAnnotations["Version"] = (cBetaVersion() ? qsl("%1 beta").arg(cBetaVersion()) : (cAlphaVersion() ? qsl("%1 alpha") : qsl("%1")).arg(AppVersion)).toUtf8().constData();
ProcessAnnotations["Launched"] = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss").toUtf8().constData();
ProcessAnnotations["Platform"] = cPlatformString().toUtf8().constData();
ProcessAnnotations["UserTag"] = QString::number(Sandbox::UserTag(), 16).toUtf8().constData();
QString dumpspath = cWorkingDir() + qsl("tdata/dumps");
QDir().mkpath(dumpspath);
#ifdef Q_OS_WIN
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
dumpspath.toStdWString(),
google_breakpad::ExceptionHandler::FilterCallback(nullptr),
DumpCallback,
(void*)nullptr, // callback_context
google_breakpad::ExceptionHandler::HANDLER_ALL,
MINIDUMP_TYPE(MiniDumpNormal),
// MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithProcessThreadData | MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules | MiniDumpWithFullAuxiliaryState | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithTokenInformation),
(const wchar_t*)nullptr, // pipe_name
(const google_breakpad::CustomClientInfo*)nullptr
);
#elif defined Q_OS_MAC // Q_OS_WIN
#ifdef MAC_USE_BREAKPAD
#ifndef _DEBUG
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
QFile::encodeName(dumpspath).toStdString(),
/*FilterCallback*/ 0,
DumpCallback,
/*context*/ 0,
true,
0
);
#endif // !_DEBUG
SetSignalHandlers = false;
#else // MAC_USE_BREAKPAD
crashpad::CrashpadClient crashpad_client;
std::string handler = (cExeDir() + cExeName() + qsl("/Contents/Helpers/crashpad_handler")).toUtf8().constData();
std::string database = QFile::encodeName(dumpspath).constData();
if (crashpad_client.StartHandler(base::FilePath(handler),
base::FilePath(database),
std::string(),
ProcessAnnotations,
std::vector<std::string>(),
false)) {
crashpad_client.UseHandler();
}
#endif // else for MAC_USE_BREAKPAD
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
google_breakpad::MinidumpDescriptor(QFile::encodeName(dumpspath).toStdString()),
/*FilterCallback*/ 0,
DumpCallback,
/*context*/ 0,
true,
-1
);
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
}
void FinishCatching() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
#if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
delete base::take(BreakpadExceptionHandler);
#endif // !Q_OS_MAC || MAC_USE_BREAKPAD
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
}
Status Start() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
ReportPath = cWorkingDir() + qsl("tdata/working");
#ifdef Q_OS_WIN
FILE *f = nullptr;
if (_wfopen_s(&f, ReportPath.toStdWString().c_str(), L"rb") != 0) {
f = nullptr;
} else {
#else // !Q_OS_WIN
if (FILE *f = fopen(QFile::encodeName(ReportPath).constData(), "rb")) {
#endif // else for !Q_OS_WIN
QByteArray lastdump;
char buffer[256 * 1024] = { 0 };
int32 read = fread(buffer, 1, 256 * 1024, f);
if (read > 0) {
lastdump.append(buffer, read);
}
fclose(f);
Sandbox::SetLastCrashDump(lastdump);
LOG(("Opened '%1' for reading, the previous Telegram Desktop launch was not finished properly :( Crash log size: %2").arg(ReportPath).arg(lastdump.size()));
return LastCrashed;
}
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
return Restart();
}
Status Restart() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
if (ReportFile) {
return Started;
}
#ifdef Q_OS_WIN
if (_wfopen_s(&ReportFile, ReportPath.toStdWString().c_str(), L"wb") != 0) {
ReportFile = nullptr;
}
#else // Q_OS_WIN
ReportFile = fopen(QFile::encodeName(ReportPath).constData(), "wb");
#endif // else for Q_OS_WIN
if (ReportFile) {
#ifdef Q_OS_WIN
ReportFileNo = _fileno(ReportFile);
#else // Q_OS_WIN
ReportFileNo = fileno(ReportFile);
#endif // else for Q_OS_WIN
if (SetSignalHandlers) {
#ifndef Q_OS_WIN
struct sigaction sigact;
sigact.sa_sigaction = SignalHandler;
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
sigaction(SIGABRT, &sigact, &SIG_def[SIGABRT]);
sigaction(SIGSEGV, &sigact, &SIG_def[SIGSEGV]);
sigaction(SIGILL, &sigact, &SIG_def[SIGILL]);
sigaction(SIGFPE, &sigact, &SIG_def[SIGFPE]);
sigaction(SIGBUS, &sigact, &SIG_def[SIGBUS]);
sigaction(SIGSYS, &sigact, &SIG_def[SIGSYS]);
#else // !Q_OS_WIN
signal(SIGABRT, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGFPE, SignalHandler);
#endif // else for !Q_OS_WIN
}
InstallOperatorNewHandler();
return Started;
}
LOG(("FATAL: Could not open '%1' for writing!").arg(ReportPath));
return CantOpen;
#else // !TDESKTOP_DISABLE_CRASH_REPORTS
return Started;
#endif // else for !TDESKTOP_DISABLE_CRASH_REPORTS
}
void Finish() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
FinishCatching();
if (ReportFile) {
fclose(ReportFile);
ReportFile = nullptr;
#ifdef Q_OS_WIN
_wunlink(ReportPath.toStdWString().c_str());
#else // Q_OS_WIN
unlink(ReportPath.toUtf8().constData());
#endif // else for Q_OS_WIN
}
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
}
void SetAnnotation(const std::string &key, const QString &value) {
static QMutex mutex;
QMutexLocker lock(&mutex);
if (!value.trimmed().isEmpty()) {
ProcessAnnotations[key] = value.toUtf8().constData();
} else {
ProcessAnnotations.erase(key);
}
}
void SetAnnotationRef(const std::string &key, const QString *valuePtr) {
if (valuePtr) {
ProcessAnnotationRefs[key] = valuePtr;
} else {
ProcessAnnotationRefs.erase(key);
}
}
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
dump::~dump() {
if (ReportFile) {
fflush(ReportFile);
}
}
const dump &operator<<(const dump &stream, const char *str) {
if (!ReportFile) return stream;
fwrite(str, 1, strlen(str), ReportFile);
return stream;
}
const dump &operator<<(const dump &stream, const wchar_t *str) {
if (!ReportFile) return stream;
for (int i = 0, l = wcslen(str); i < l; ++i) {
if (str[i] >= 0 && str[i] < 128) {
SafeWriteChar(char(str[i]));
} else {
SafeWriteChar('?');
}
}
return stream;
}
const dump &operator<<(const dump &stream, int num) {
return SafeWriteNumber(stream, num);
}
const dump &operator<<(const dump &stream, unsigned int num) {
return SafeWriteNumber(stream, num);
}
const dump &operator<<(const dump &stream, unsigned long num) {
return SafeWriteNumber(stream, num);
}
const dump &operator<<(const dump &stream, unsigned long long num) {
return SafeWriteNumber(stream, num);
}
const dump &operator<<(const dump &stream, double num) {
if (num < 0) {
SafeWriteChar('-');
num = -num;
}
SafeWriteNumber(stream, uint64(floor(num)));
SafeWriteChar('.');
num -= floor(num);
for (int i = 0; i < 4; ++i) {
num *= 10;
int digit = int(floor(num));
SafeWriteChar('0' + digit);
num -= digit;
}
return stream;
}
#endif // TDESKTOP_DISABLE_CRASH_REPORTS
} // namespace CrashReports

View File

@@ -0,0 +1,85 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
namespace CrashReports {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
struct dump {
~dump();
};
const dump &operator<<(const dump &stream, const char *str);
const dump &operator<<(const dump &stream, const wchar_t *str);
const dump &operator<<(const dump &stream, int num);
const dump &operator<<(const dump &stream, unsigned int num);
const dump &operator<<(const dump &stream, unsigned long num);
const dump &operator<<(const dump &stream, unsigned long long num);
const dump &operator<<(const dump &stream, double num);
#endif // TDESKTOP_DISABLE_CRASH_REPORTS
enum Status {
CantOpen,
LastCrashed,
Started
};
Status Start();
Status Restart(); // can be only CantOpen or Started
void Finish();
void SetAnnotation(const std::string &key, const QString &value);
inline void ClearAnnotation(const std::string &key) {
SetAnnotation(key, QString());
}
// Remembers value pointer and tries to add the value to the crash report.
// Attention! You should call clearCrashAnnotationRef(key) before destroying value.
void SetAnnotationRef(const std::string &key, const QString *valuePtr);
inline void ClearAnnotationRef(const std::string &key) {
SetAnnotationRef(key, nullptr);
}
void StartCatching();
void FinishCatching();
} // namespace CrashReports
namespace base {
namespace assertion {
inline void log(const char *message, const char *file, int line) {
const auto info = QStringLiteral("%1 %2:%3"
).arg(message
).arg(file
).arg(line
);
const auto entry = QStringLiteral("Assertion Failed! ") + info;
#ifdef LOG
LOG((entry));
#endif // LOG
CrashReports::SetAnnotation("Assertion", info);
}
} // namespace assertion
} // namespace base

View File

@@ -23,10 +23,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwindow.h"
#include "storage/localstorage.h"
#include "platform/platform_file_utilities.h"
#include "base/task_queue.h"
#include "messenger.h"
bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &initialPath) {
bool filedialogGetSaveFile(
QString &file,
const QString &caption,
const QString &filter,
const QString &initialPath) {
QStringList files;
QByteArray remoteContent;
bool result = Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::WriteFile, initialPath);
@@ -34,7 +37,12 @@ bool filedialogGetSaveFile(QString &file, const QString &caption, const QString
return result;
}
QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path, bool skipExistance, int fileTime) {
QString filedialogDefaultName(
const QString &prefix,
const QString &extension,
const QString &path,
bool skipExistance,
int fileTime) {
auto directoryPath = path;
if (directoryPath.isEmpty()) {
if (cDialogLastPath().isEmpty()) {
@@ -69,7 +77,10 @@ QString filedialogDefaultName(const QString &prefix, const QString &extension, c
return name;
}
QString filedialogNextFilename(const QString &name, const QString &cur, const QString &path) {
QString filedialogNextFilename(
const QString &name,
const QString &cur,
const QString &path) {
QDir dir(path.isEmpty() ? cDialogLastPath() : path);
int32 extIndex = name.lastIndexOf('.');
QString prefix = name, extension;
@@ -87,13 +98,13 @@ QString filedialogNextFilename(const QString &name, const QString &cur, const QS
namespace File {
void OpenEmailLink(const QString &email) {
base::TaskQueue::Main().Put([email] {
crl::on_main([=] {
Platform::File::UnsafeOpenEmailLink(email);
});
}
void OpenWith(const QString &filepath, QPoint menuPosition) {
base::TaskQueue::Main().Put([filepath, menuPosition] {
crl::on_main([=] {
if (!Platform::File::UnsafeShowOpenWithDropdown(filepath, menuPosition)) {
if (!Platform::File::UnsafeShowOpenWith(filepath)) {
Platform::File::UnsafeLaunch(filepath);
@@ -103,13 +114,13 @@ void OpenWith(const QString &filepath, QPoint menuPosition) {
}
void Launch(const QString &filepath) {
base::TaskQueue::Main().Put([filepath] {
crl::on_main([=] {
Platform::File::UnsafeLaunch(filepath);
});
}
void ShowInFolder(const QString &filepath) {
base::TaskQueue::Main().Put([filepath] {
crl::on_main([=] {
Platform::File::UnsafeShowInFolder(filepath);
});
}
@@ -130,19 +141,30 @@ void UnsafeLaunchDefault(const QString &filepath) {
namespace FileDialog {
void GetOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed) {
base::TaskQueue::Main().Put([caption, filter, callback = std::move(callback), failed = std::move(failed)] {
void GetOpenPath(
const QString &caption,
const QString &filter,
base::lambda<void(OpenResult &&result)> callback,
base::lambda<void()> failed) {
crl::on_main([=] {
auto files = QStringList();
auto remoteContent = QByteArray();
if (Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFile)
&& ((!files.isEmpty() && !files[0].isEmpty()) || !remoteContent.isEmpty())) {
const auto success = Platform::FileDialog::Get(
files,
remoteContent,
caption,
filter,
FileDialog::internal::Type::ReadFile);
if (success
&& ((!files.isEmpty() && !files[0].isEmpty())
|| !remoteContent.isEmpty())) {
if (callback) {
auto result = OpenResult();
if (!files.isEmpty() && !files[0].isEmpty()) {
result.paths.push_back(files[0]);
}
result.remoteContent = remoteContent;
callback(result);
callback(std::move(result));
}
} else if (failed) {
failed();
@@ -150,17 +172,26 @@ void GetOpenPath(const QString &caption, const QString &filter, base::lambda<voi
});
}
void GetOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed) {
base::TaskQueue::Main().Put([caption, filter, callback = std::move(callback), failed = std::move(failed)] {
void GetOpenPaths(
const QString &caption,
const QString &filter,
base::lambda<void(OpenResult &&result)> callback,
base::lambda<void()> failed) {
crl::on_main([=] {
auto files = QStringList();
auto remoteContent = QByteArray();
if (Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFiles)
&& (!files.isEmpty() || !remoteContent.isEmpty())) {
const auto success = Platform::FileDialog::Get(
files,
remoteContent,
caption,
filter,
FileDialog::internal::Type::ReadFiles);
if (success && (!files.isEmpty() || !remoteContent.isEmpty())) {
if (callback) {
auto result = OpenResult();
result.paths = files;
result.remoteContent = remoteContent;
callback(result);
callback(std::move(result));
}
} else if (failed) {
failed();
@@ -168,12 +199,17 @@ void GetOpenPaths(const QString &caption, const QString &filter, base::lambda<vo
});
}
void GetWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed) {
base::TaskQueue::Main().Put([caption, filter, initialPath, callback = std::move(callback), failed = std::move(failed)] {
void GetWritePath(
const QString &caption,
const QString &filter,
const QString &initialPath,
base::lambda<void(QString &&result)> callback,
base::lambda<void()> failed) {
crl::on_main([=] {
auto file = QString();
if (filedialogGetSaveFile(file, caption, filter, initialPath)) {
if (callback) {
callback(file);
callback(std::move(file));
}
} else if (failed) {
failed();
@@ -181,14 +217,24 @@ void GetWritePath(const QString &caption, const QString &filter, const QString &
});
}
void GetFolder(const QString &caption, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed) {
base::TaskQueue::Main().Put([caption, initialPath, callback = std::move(callback), failed = std::move(failed)] {
void GetFolder(
const QString &caption,
const QString &initialPath,
base::lambda<void(QString &&result)> callback,
base::lambda<void()> failed) {
crl::on_main([=] {
auto files = QStringList();
auto remoteContent = QByteArray();
if (Platform::FileDialog::Get(files, remoteContent, caption, QString(), FileDialog::internal::Type::ReadFolder, initialPath)
&& !files.isEmpty() && !files[0].isEmpty()) {
const auto success = Platform::FileDialog::Get(
files,
remoteContent,
caption,
QString(),
FileDialog::internal::Type::ReadFolder,
initialPath);
if (success && !files.isEmpty() && !files[0].isEmpty()) {
if (callback) {
callback(files[0]);
callback(std::move(files[0]));
}
} else if (failed) {
failed();

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

@@ -0,0 +1,246 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "core/launcher.h"
#include "platform/platform_launcher.h"
#include "platform/platform_specific.h"
#include "core/crash_reports.h"
#include "core/main_queue_processor.h"
#include "application.h"
namespace Core {
std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) {
return std::make_unique<Platform::Launcher>(argc, argv);
}
Launcher::Launcher(int argc, char *argv[])
: _argc(argc)
, _argv(argv) {
}
void Launcher::init() {
_arguments = readArguments(_argc, _argv);
prepareSettings();
QCoreApplication::setApplicationName(qsl("TelegramDesktop"));
#ifndef OS_MAC_OLD
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
#endif // OS_MAC_OLD
initHook();
}
int Launcher::exec() {
init();
if (cLaunchMode() == LaunchModeFixPrevious) {
return psFixPrevious();
} else if (cLaunchMode() == LaunchModeCleanup) {
return psCleanup();
}
// both are finished in Application::closeApplication
Logs::start(this); // must be started before Platform is started
Platform::start(); // must be started before QApplication is created
auto result = executeApplication();
DEBUG_LOG(("Telegram finished, result: %1").arg(result));
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
if (cRestartingUpdate()) {
DEBUG_LOG(("Application Info: executing updater to install update..."));
if (!launchUpdater(UpdaterLaunch::PerformUpdate)) {
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
}
} else
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
if (cRestarting()) {
DEBUG_LOG(("Application Info: executing Telegram, because of restart..."));
launchUpdater(UpdaterLaunch::JustRelaunch);
}
CrashReports::Finish();
Platform::finish();
Logs::finish();
return result;
}
QStringList Launcher::readArguments(int argc, char *argv[]) const {
Expects(argc >= 0);
if (const auto native = readArgumentsHook(argc, argv)) {
return *native;
}
auto result = QStringList();
result.reserve(argc);
for (auto i = 0; i != argc; ++i) {
result.push_back(fromUtf8Safe(argv[i]));
}
return result;
}
QString Launcher::argumentsString() const {
return _arguments.join(' ');
}
void Launcher::prepareSettings() {
#ifdef Q_OS_MAC
#ifndef OS_MAC_OLD
if (QSysInfo::macVersion() >= QSysInfo::MV_10_11) {
gIsElCapitan = true;
}
#else // OS_MAC_OLD
if (QSysInfo::macVersion() < QSysInfo::MV_10_7) {
gIsSnowLeopard = true;
}
#endif // OS_MAC_OLD
#endif // Q_OS_MAC
switch (cPlatform()) {
case dbipWindows:
gUpdateURL = QUrl(qsl("http://tdesktop.com/win/tupdates/current"));
#ifndef OS_WIN_STORE
gPlatformString = qsl("Windows");
#else // OS_WIN_STORE
gPlatformString = qsl("WinStore");
#endif // OS_WIN_STORE
break;
case dbipMac:
gUpdateURL = QUrl(qsl("http://tdesktop.com/mac/tupdates/current"));
#ifndef OS_MAC_STORE
gPlatformString = qsl("MacOS");
#else // OS_MAC_STORE
gPlatformString = qsl("MacAppStore");
#endif // OS_MAC_STORE
break;
case dbipMacOld:
gUpdateURL = QUrl(qsl("http://tdesktop.com/mac32/tupdates/current"));
gPlatformString = qsl("MacOSold");
break;
case dbipLinux64:
gUpdateURL = QUrl(qsl("http://tdesktop.com/linux/tupdates/current"));
gPlatformString = qsl("Linux64bit");
break;
case dbipLinux32:
gUpdateURL = QUrl(qsl("http://tdesktop.com/linux32/tupdates/current"));
gPlatformString = qsl("Linux32bit");
break;
}
auto path = Platform::CurrentExecutablePath(_argc, _argv);
LOG(("Executable path before check: %1").arg(path));
if (!path.isEmpty()) {
auto info = QFileInfo(path);
if (info.isSymLink()) {
info = info.symLinkTarget();
}
if (info.exists()) {
gExeDir = info.absoluteDir().absolutePath() + '/';
gExeName = info.fileName();
}
}
if (cExeName().isEmpty()) {
LOG(("WARNING: Could not compute executable path, some features will be disabled."));
}
processArguments();
}
void Launcher::processArguments() {
enum class KeyFormat {
NoValues,
OneValue,
AllLeftValues,
};
auto parseMap = std::map<QByteArray, KeyFormat> {
{ "-testmode" , KeyFormat::NoValues },
{ "-debug" , KeyFormat::NoValues },
{ "-many" , KeyFormat::NoValues },
{ "-key" , KeyFormat::OneValue },
{ "-autostart" , KeyFormat::NoValues },
{ "-fixprevious", KeyFormat::NoValues },
{ "-cleanup" , KeyFormat::NoValues },
{ "-noupdate" , KeyFormat::NoValues },
{ "-tosettings" , KeyFormat::NoValues },
{ "-startintray", KeyFormat::NoValues },
{ "-sendpath" , KeyFormat::AllLeftValues },
{ "-workdir" , KeyFormat::OneValue },
{ "--" , KeyFormat::OneValue },
};
auto parseResult = QMap<QByteArray, QStringList>();
auto parsingKey = QByteArray();
auto parsingFormat = KeyFormat::NoValues;
for (const auto &argument : _arguments) {
switch (parsingFormat) {
case KeyFormat::OneValue: {
parseResult[parsingKey] = QStringList(argument.mid(0, 8192));
parsingFormat = KeyFormat::NoValues;
} break;
case KeyFormat::AllLeftValues: {
parseResult[parsingKey].push_back(argument.mid(0, 8192));
} break;
case KeyFormat::NoValues: {
parsingKey = argument.toLatin1();
auto it = parseMap.find(parsingKey);
if (it != parseMap.end()) {
parsingFormat = it->second;
parseResult[parsingKey] = QStringList();
}
} break;
}
}
gTestMode = parseResult.contains("-testmode");
gDebug = parseResult.contains("-debug");
gManyInstance = parseResult.contains("-many");
gKeyFile = parseResult.value("-key", QStringList()).join(QString());
gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart
: parseResult.contains("-fixprevious") ? LaunchModeFixPrevious
: parseResult.contains("-cleanup") ? LaunchModeCleanup
: LaunchModeNormal;
gNoStartUpdate = parseResult.contains("-noupdate");
gStartToSettings = parseResult.contains("-tosettings");
gStartInTray = parseResult.contains("-startintray");
gSendPaths = parseResult.value("-sendpath", QStringList());
gWorkingDir = parseResult.value("-workdir", QStringList()).join(QString());
if (!gWorkingDir.isEmpty()) {
if (QDir().exists(gWorkingDir)) {
_customWorkingDir = true;
} else {
gWorkingDir = QString();
}
}
gStartUrl = parseResult.value("--", QStringList()).join(QString());
}
int Launcher::executeApplication() {
MainQueueProcessor processor;
Application app(this, _argc, _argv);
return app.exec();
}
} // namespace Core

View File

@@ -0,0 +1,71 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
namespace Core {
class Launcher {
public:
Launcher(int argc, char *argv[]);
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
int exec();
QString argumentsString() const;
bool customWorkingDir() const {
return _customWorkingDir;
}
protected:
enum class UpdaterLaunch {
PerformUpdate,
JustRelaunch,
};
private:
void prepareSettings();
void processArguments();
QStringList readArguments(int argc, char *argv[]) const;
virtual base::optional<QStringList> readArgumentsHook(
int argc,
char *argv[]) const {
return base::none;
}
void init();
virtual void initHook() {
}
virtual bool launchUpdater(UpdaterLaunch action) = 0;
int executeApplication();
int _argc;
char **_argv;
QStringList _arguments;
bool _customWorkingDir = false;
};
} // namespace Core

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 = 1002000;
constexpr str_const AppVersionStr = "1.2";
constexpr bool AppAlphaVersion = false;
constexpr int AppVersion = 1002007;
constexpr str_const AppVersionStr = "1.2.7";
constexpr bool AppAlphaVersion = true;
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;

View File

@@ -224,7 +224,10 @@ QString documentSaveFilename(const DocumentData *data, bool forceSavingAs = fals
return saveFileName(caption, filter, prefix, name, forceSavingAs, dir);
}
void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context, ActionOnLoad action) {
void DocumentOpenClickHandler::doOpen(
not_null<DocumentData*> data,
HistoryItem *context,
ActionOnLoad action) {
if (!data->date) return;
auto msgId = context ? context->fullId() : FullMsgId();
@@ -329,23 +332,20 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
}
void DocumentOpenClickHandler::onClickImpl() const {
const auto item = App::hoveredLinkItem()
? App::hoveredLinkItem()
: (App::contextItem() ? App::contextItem() : nullptr);
const auto action = document()->isVoiceMessage()
const auto data = document();
const auto action = data->isVoiceMessage()
? ActionOnLoadNone
: ActionOnLoadOpen;
doOpen(document(), item, action);
doOpen(data, getActionItem(), action);
}
void GifOpenClickHandler::onClickImpl() const {
const auto item = App::hoveredLinkItem()
? App::hoveredLinkItem()
: (App::contextItem() ? App::contextItem() : nullptr);
doOpen(document(), item, ActionOnLoadPlayInline);
doOpen(document(), getActionItem(), ActionOnLoadPlayInline);
}
void DocumentSaveClickHandler::doSave(DocumentData *data, bool forceSavingAs) {
void DocumentSaveClickHandler::doSave(
not_null<DocumentData*> data,
bool forceSavingAs) {
if (!data->date) return;
auto filepath = data->filepath(DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs);
@@ -369,17 +369,13 @@ void DocumentSaveClickHandler::onClickImpl() const {
}
void DocumentCancelClickHandler::onClickImpl() const {
auto data = document();
const auto data = document();
if (!data->date) return;
if (data->uploading()) {
if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) {
if (auto media = item->getMedia()) {
if (media->getDocument() == data) {
App::contextItem(item);
App::main()->cancelUploadLayer();
}
}
if (const auto item = App::histItemById(context())) {
App::contextItem(item);
App::main()->cancelUploadLayer();
}
} else {
data->cancel();
@@ -672,12 +668,19 @@ QString DocumentData::loadingFilePath() const {
}
bool DocumentData::displayLoading() const {
return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : uploading();
return loading()
? (!_loader->loadingLocal() || !_loader->autoLoading())
: (uploading() && !waitingForAlbum());
}
float64 DocumentData::progress() const {
if (uploading()) {
return snap((size > 0) ? float64(uploadOffset) / size : 0., 0., 1.);
if (uploadingData->size > 0) {
const auto result = float64(uploadingData->offset)
/ uploadingData->size;
return snap(result, 0., 1.);
}
return 0.;
}
return loading() ? _loader->currentProgress() : (loaded() ? 1. : 0.);
}
@@ -687,7 +690,17 @@ int32 DocumentData::loadOffset() const {
}
bool DocumentData::uploading() const {
return status == FileUploading;
return (uploadingData != nullptr);
}
void DocumentData::setWaitingForAlbum() {
if (uploading()) {
uploadingData->waitingForAlbum = true;
}
}
bool DocumentData::waitingForAlbum() const {
return uploading() && uploadingData->waitingForAlbum;
}
void DocumentData::save(const QString &toFile, ActionOnLoad action, const FullMsgId &actionMsgId, LoadFromCloudSetting fromCloud, bool autoLoading) {

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,17 +313,20 @@ private:
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit);
QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);
class DocumentClickHandler : public LeftButtonClickHandler {
class DocumentClickHandler : public FileClickHandler {
public:
DocumentClickHandler(DocumentData *document)
: _document(document) {
DocumentClickHandler(
not_null<DocumentData*> document,
FullMsgId context = FullMsgId())
: FileClickHandler(context)
, _document(document) {
}
DocumentData *document() const {
not_null<DocumentData*> document() const {
return _document;
}
private:
DocumentData *_document;
not_null<DocumentData*> _document;
};
@@ -327,7 +334,7 @@ class DocumentSaveClickHandler : public DocumentClickHandler {
public:
using DocumentClickHandler::DocumentClickHandler;
static void doSave(
DocumentData *document,
not_null<DocumentData*> document,
bool forceSavingAs = false);
protected:
@@ -339,7 +346,7 @@ class DocumentOpenClickHandler : public DocumentClickHandler {
public:
using DocumentClickHandler::DocumentClickHandler;
static void doOpen(
DocumentData *document,
not_null<DocumentData*> document,
HistoryItem *context,
ActionOnLoad action = ActionOnLoadOpen);

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(
@@ -277,12 +278,7 @@ void PeerData::updateUserpic(
const auto size = kUserpicSize;
const auto loc = StorageImageLocation::FromMTP(size, size, location);
const auto photo = loc.isNull() ? ImagePtr() : ImagePtr(loc);
if (_userpicPhotoId != photoId
|| _userpic.v() != photo.v()
|| _userpicLocation != loc) {
setUserpic(photoId, loc, photo);
Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged);
}
setUserpicChecked(photoId, loc, photo);
}
void PeerData::clearUserpic() {
@@ -300,6 +296,19 @@ void PeerData::clearUserpic() {
}
return ImagePtr();
}();
setUserpicChecked(photoId, loc, photo);
}
void PeerData::setUserpicChecked(
PhotoId photoId,
const StorageImageLocation &location,
ImagePtr userpic) {
if (_userpicPhotoId != photoId
|| _userpic.v() != userpic.v()
|| _userpicLocation != location) {
setUserpic(photoId, location, userpic);
Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged);
}
}
void PeerData::fillNames() {
@@ -343,7 +352,10 @@ PeerData::~PeerData() = default;
const Text &BotCommand::descriptionText() const {
if (_descriptionText.isEmpty() && !_description.isEmpty()) {
_descriptionText.setText(st::defaultTextStyle, _description, _textNameOptions);
_descriptionText.setText(
st::defaultTextStyle,
_description,
Ui::NameTextOptions());
}
return _descriptionText;
}
@@ -483,7 +495,10 @@ void UserData::setBotInfo(const MTPBotInfo &info) {
void UserData::setNameOrPhone(const QString &newNameOrPhone) {
if (nameOrPhone != newNameOrPhone) {
nameOrPhone = newNameOrPhone;
phoneText.setText(st::msgNameStyle, nameOrPhone, _textNameOptions);
phoneText.setText(
st::msgNameStyle,
nameOrPhone,
Ui::NameTextOptions());
}
}
@@ -568,16 +583,18 @@ void ChatData::setInviteLink(const QString &newInviteLink) {
ChannelData::ChannelData(const PeerId &id)
: PeerData(id)
, inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))) {
Data::PeerFlagValue(this, MTPDchannel::Flag::f_megagroup)
| rpl::start_with_next([this](bool megagroup) {
if (megagroup) {
if (!mgInfo) {
mgInfo = std::make_unique<MegagroupInfo>();
}
} else if (mgInfo) {
mgInfo = nullptr;
Data::PeerFlagValue(
this,
MTPDchannel::Flag::f_megagroup
) | rpl::start_with_next([this](bool megagroup) {
if (megagroup) {
if (!mgInfo) {
mgInfo = std::make_unique<MegagroupInfo>();
}
}, _lifetime);
} else if (mgInfo) {
mgInfo = nullptr;
}
}, _lifetime);
}
void ChannelData::setPhoto(const MTPChatPhoto &photo) {
@@ -784,9 +801,12 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChannelBann
}
Data::ChannelAdminChanges(this).feed(peerToUser(user->id), false);
} else {
if (isKicked && membersCount() > 1) {
setMembersCount(membersCount() - 1);
flags |= Notify::PeerUpdate::Flag::MembersChanged;
if (isKicked) {
if (membersCount() > 1) {
setMembersCount(membersCount() - 1);
flags |= Notify::PeerUpdate::Flag::MembersChanged;
}
setKickedCount(kickedCount() + 1);
}
}
Notify::peerUpdatedDelayed(this, flags);

View File

@@ -238,6 +238,11 @@ private:
std::unique_ptr<Ui::EmptyUserpic> createEmptyUserpic() const;
void refreshEmptyUserpic() const;
void setUserpicChecked(
PhotoId photoId,
const StorageImageLocation &location,
ImagePtr userpic);
static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL);
ImagePtr _userpic;

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() {
@@ -121,7 +133,7 @@ ImagePtr PhotoData::makeReplyPreview() {
}
void PhotoOpenClickHandler::onClickImpl() const {
Messenger::Instance().showPhoto(this, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem());
Messenger::Instance().showPhoto(this);
}
void PhotoSaveClickHandler::onClickImpl() const {
@@ -136,13 +148,9 @@ void PhotoCancelClickHandler::onClickImpl() const {
if (!data->date) return;
if (data->uploading()) {
if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) {
if (auto media = item->getMedia()) {
if (media->type() == MediaTypePhoto && static_cast<HistoryPhoto*>(media)->photo() == data) {
App::contextItem(item);
App::main()->cancelUploadLayer();
}
}
if (const auto item = App::histItemById(context())) {
App::contextItem(item);
App::main()->cancelUploadLayer();
}
} else {
data->cancel();

View File

@@ -44,6 +44,9 @@ public:
int32 loadOffset() const;
bool uploading() const;
void setWaitingForAlbum();
bool waitingForAlbum() const;
void forget();
ImagePtr makeReplyPreview();
@@ -57,25 +60,22 @@ public:
PeerData *peer = nullptr; // for chat and channel photos connection
// geo, caption
struct UploadingData {
UploadingData(int size) : size(size) {
}
int offset = 0;
int size = 0;
};
std::unique_ptr<UploadingData> uploadingData;
std::unique_ptr<Data::UploadState> uploadingData;
private:
void notifyLayoutChanged() const;
};
class PhotoClickHandler : public LeftButtonClickHandler {
class PhotoClickHandler : public FileClickHandler {
public:
PhotoClickHandler(
not_null<PhotoData*> photo,
FullMsgId context = FullMsgId(),
PeerData *peer = nullptr)
: _photo(photo), _peer(peer) {
: FileClickHandler(context)
, _photo(photo)
, _peer(peer) {
}
not_null<PhotoData*> photo() const {
return _photo;
@@ -86,7 +86,7 @@ public:
private:
not_null<PhotoData*> _photo;
PeerData *_peer;
PeerData *_peer = nullptr;
};

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;
};
@@ -290,9 +284,7 @@ base::optional<bool> SharedMediaWithLastSlice::IsLastIsolated(
| [](FullMsgId msgId) { return App::histItemById(msgId); }
| [](HistoryItem *item) { return item ? item->getMedia() : nullptr; }
| [](HistoryMedia *media) {
return (media && media->type() == MediaTypePhoto)
? static_cast<HistoryPhoto*>(media)->photo()
: nullptr;
return media ? media->getPhoto() : nullptr;
}
| [](PhotoData *photo) { return photo ? photo->id : 0; }
| [&](PhotoId photoId) { return *lastPeerPhotoId != photoId; };
@@ -355,9 +347,12 @@ rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastReversedViewer(
SharedMediaWithLastSlice::Key key,
int limitBefore,
int limitAfter) {
return SharedMediaWithLastViewer(key, limitBefore, limitAfter)
| rpl::map([](SharedMediaWithLastSlice &&slice) {
slice.reverse();
return std::move(slice);
});
return SharedMediaWithLastViewer(
key,
limitBefore,
limitAfter
) | rpl::map([](SharedMediaWithLastSlice &&slice) {
slice.reverse();
return std::move(slice);
});
}

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

@@ -0,0 +1,122 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "data/data_web_page.h"
#include "auth_session.h"
#include "apiwrap.h"
#include "mainwidget.h"
#include "ui/text/text_entity.h"
namespace {
QString SiteNameFromUrl(const QString &url) {
QUrl u(url);
QString pretty = u.isValid() ? u.toDisplayString() : url;
QRegularExpressionMatch m = QRegularExpression(qsl("^[a-zA-Z0-9]+://")).match(pretty);
if (m.hasMatch()) pretty = pretty.mid(m.capturedLength());
int32 slash = pretty.indexOf('/');
if (slash > 0) pretty = pretty.mid(0, slash);
QStringList components = pretty.split('.', QString::SkipEmptyParts);
if (components.size() >= 2) {
components = components.mid(components.size() - 2);
return components.at(0).at(0).toUpper() + components.at(0).mid(1) + '.' + components.at(1);
}
return QString();
}
} // namespace
bool WebPageData::applyChanges(
const QString &newType,
const QString &newUrl,
const QString &newDisplayUrl,
const QString &newSiteName,
const QString &newTitle,
const TextWithEntities &newDescription,
PhotoData *newPhoto,
DocumentData *newDocument,
int newDuration,
const QString &newAuthor,
int newPendingTill) {
if (newPendingTill != 0
&& (!url.isEmpty() || newUrl.isEmpty())
&& (!pendingTill
|| pendingTill == newPendingTill
|| newPendingTill < -1)) {
return false;
}
const auto resultType = toWebPageType(newType);
const auto resultUrl = TextUtilities::Clean(newUrl);
const auto resultDisplayUrl = TextUtilities::Clean(
newDisplayUrl);
const auto possibleSiteName = TextUtilities::Clean(
newSiteName);
const auto resultTitle = TextUtilities::SingleLine(
newTitle);
const auto resultAuthor = TextUtilities::Clean(newAuthor);
const auto viewTitleText = resultTitle.isEmpty()
? TextUtilities::SingleLine(resultAuthor)
: resultTitle;
const auto resultSiteName = [&] {
if (!possibleSiteName.isEmpty()) {
return possibleSiteName;
} else if (!newDescription.text.isEmpty()
&& viewTitleText.isEmpty()
&& !resultUrl.isEmpty()) {
return SiteNameFromUrl(resultUrl);
}
return QString();
}();
if (type == resultType
&& url == resultUrl
&& displayUrl == resultDisplayUrl
&& siteName == resultSiteName
&& title == resultTitle
&& description.text == newDescription.text
&& photo == newPhoto
&& document == newDocument
&& duration == newDuration
&& author == resultAuthor
&& pendingTill == newPendingTill) {
return false;
}
if (pendingTill > 0 && newPendingTill <= 0) {
Auth().api().clearWebPageRequest(this);
}
type = resultType;
url = resultUrl;
displayUrl = resultDisplayUrl;
siteName = resultSiteName;
title = resultTitle;
description = newDescription;
photo = newPhoto;
document = newDocument;
duration = newDuration;
author = resultAuthor;
pendingTill = newPendingTill;
++version;
if (App::main()) App::main()->webPageUpdated(this);
return true;
}

View File

@@ -50,9 +50,9 @@ struct WebPageData {
const TextWithEntities &description,
DocumentData *document,
PhotoData *photo,
int32 duration,
int duration,
const QString &author,
int32 pendingTill)
int pendingTill)
: id(id)
, type(type)
, url(url)
@@ -72,6 +72,19 @@ struct WebPageData {
if (photo) photo->forget();
}
bool applyChanges(
const QString &newType,
const QString &newUrl,
const QString &newDisplayUrl,
const QString &newSiteName,
const QString &newTitle,
const TextWithEntities &newDescription,
PhotoData *newPhoto,
DocumentData *newDocument,
int newDuration,
const QString &newAuthor,
int newPendingTill);
WebPageId id = 0;
WebPageType type = WebPageArticle;
QString url;
@@ -79,10 +92,11 @@ struct WebPageData {
QString siteName;
QString title;
TextWithEntities description;
int32 duration = 0;
int duration = 0;
QString author;
PhotoData *photo = nullptr;
DocumentData *document = nullptr;
int32 pendingTill = 0;
int pendingTill = 0;
int version = 0;
};

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"
@@ -36,6 +37,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "apiwrap.h"
#include "window/themes/window_theme.h"
#include "observer_peer.h"
#include "chat_helpers/stickers.h"
#include "auth_session.h"
#include "window/notifications_manager.h"
#include "window/window_controller.h"
@@ -95,16 +97,16 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
_cancelSearchFromUser->hide();
subscribe(Auth().downloaderTaskFinished(), [this] { update(); });
Auth().data().itemRemoved()
| rpl::start_with_next(
[this](auto item) { itemRemoved(item); },
lifetime());
Auth().data().itemRepaintRequest()
| rpl::start_with_next([this](auto item) {
if (item->history()->lastMsg == item) {
item->history()->updateChatListEntry();
}
}, lifetime());
Auth().data().itemRemoved(
) | rpl::start_with_next(
[this](auto item) { itemRemoved(item); },
lifetime());
Auth().data().itemRepaintRequest(
) | rpl::start_with_next([this](auto item) {
if (item->history()->lastMsg == item) {
item->history()->updateChatListEntry();
}
}, lifetime());
subscribe(App::histories().sendActionAnimationUpdated(), [this](const Histories::SendActionAnimationUpdate &update) {
auto updateRect = Dialogs::Layout::RowPainter::sendActionAnimationRect(update.width, update.height, getFullWidth(), update.textUpdated);
updateDialogRow(update.history->peer, MsgId(0), updateRect, UpdateRowSection::Default | UpdateRowSection::Filtered);
@@ -1796,9 +1798,9 @@ void DialogsInner::searchInPeer(PeerData *peer, UserData *from) {
_cancelSearchInPeer->show();
if (_searchInPeer->isSelf()) {
_searchInSavedText.setText(
st::dialogsSearchFromStyle,
st::msgNameStyle,
lang(lng_saved_messages),
_textDlgOptions);
Ui::DialogTextOptions());
}
} else {
_cancelSearchInPeer->hide();
@@ -1810,7 +1812,7 @@ void DialogsInner::searchInPeer(PeerData *peer, UserData *from) {
_searchFromUserText.setText(
st::dialogsSearchFromStyle,
fromUserText,
_textDlgOptions);
Ui::DialogTextOptions());
_cancelSearchFromUser->show();
} else {
_cancelSearchFromUser->hide();
@@ -2123,7 +2125,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) {
recent = cRecentSearchHashtags();
}
found = true;
incrementRecentHashtag(recent, text.mid(i + 1, next - i - 1));
Stickers::IncrementRecentHashtag(recent, text.mid(i + 1, next - i - 1));
}
if (found) {
cSetRecentSearchHashtags(recent);

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"
@@ -34,7 +35,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "window/layer_widget.h"
#include "lang/lang_keys.h"
#include "base/observer.h"
#include "base/task_queue.h"
#include "history/history_media.h"
#include "styles/style_history.h"
@@ -69,19 +69,22 @@ bool insertBotCommand(const QString &cmd) {
return false;
}
void activateBotCommand(const HistoryItem *msg, int row, int col) {
const HistoryMessageReplyMarkup::Button *button = nullptr;
void activateBotCommand(
not_null<const HistoryItem*> msg,
int row,
int column) {
const HistoryMessageMarkupButton *button = nullptr;
if (auto markup = msg->Get<HistoryMessageReplyMarkup>()) {
if (row < markup->rows.size()) {
auto &buttonRow = markup->rows[row];
if (col < buttonRow.size()) {
button = &buttonRow.at(col);
if (column < buttonRow.size()) {
button = &buttonRow[column];
}
}
}
if (!button) return;
using ButtonType = HistoryMessageReplyMarkup::Button::Type;
using ButtonType = HistoryMessageMarkupButton::Type;
switch (button->type) {
case ButtonType::Default: {
// Copy string before passing it to the sending method
@@ -93,7 +96,7 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) {
case ButtonType::Callback:
case ButtonType::Game: {
if (auto m = main()) {
m->app_sendBotCallback(button, msg, row, col);
m->app_sendBotCallback(button, msg, row, column);
}
} break;
@@ -504,20 +507,7 @@ void WorkingDirReady() {
}
}
object_ptr<SingleQueuedInvokation> MainThreadTaskHandler = { nullptr };
void MainThreadTaskAdded() {
if (!started()) {
return;
}
MainThreadTaskHandler->call();
}
void start() {
MainThreadTaskHandler.create([] {
base::TaskQueue::ProcessMainTasks();
});
SandboxData = std::make_unique<internal::Data>();
}
@@ -527,7 +517,6 @@ bool started() {
void finish() {
SandboxData.reset();
MainThreadTaskHandler.destroy();
}
uint64 UserTag() {

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,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "data/data_channel_admins.h"
#include "ui/text_options.h"
#include "core/crash_reports.h"
namespace {
@@ -60,7 +63,7 @@ auto GlobalPinnedIndex = 0;
HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from) {
auto text = TextWithEntities { lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org")) };
TextUtilities::ParseEntities(text, _historyTextNoMonoOptions.flags);
TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags);
text.entities.push_front(EntityInText(EntityInTextItalic, 0, text.text.size()));
flags &= ~MTPDmessage::Flag::f_post_author;
return HistoryMessage::create(history, msgId, flags, replyTo, viaBotId, date, from, QString(), text);
@@ -68,11 +71,6 @@ HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage
} // namespace
void HistoryInit() {
HistoryInitMessages();
HistoryInitMedia();
}
History::History(const PeerId &peerId)
: peer(App::peer(peerId))
, lastItemTextCache(st::dialogsTextWidthMin)
@@ -362,7 +360,10 @@ bool History::updateSendActionNeedsAnimating(TimeMs ms, bool force) {
}
if (_sendActionString != newTypingString) {
_sendActionString = newTypingString;
_sendActionText.setText(st::dialogsTextStyle, _sendActionString, _textNameOptions);
_sendActionText.setText(
st::dialogsTextStyle,
_sendActionString,
Ui::NameTextOptions());
}
}
auto result = (!_typing.isEmpty() || !_sendActions.isEmpty());
@@ -542,27 +543,6 @@ const QDateTime &ChannelHistory::maxReadMessageDate() {
return _maxReadMessageDate;
}
HistoryItem *ChannelHistory::addNewChannelMessage(const MTPMessage &msg, NewMessageType type) {
if (type == NewMessageExisting) return addToHistory(msg);
return addNewToBlocks(msg, type);
}
HistoryItem *ChannelHistory::addNewToBlocks(const MTPMessage &msg, NewMessageType type) {
if (!loadedAtBottom()) {
HistoryItem *item = addToHistory(msg);
if (item) {
setLastMessage(item);
if (type == NewMessageUnread) {
newItemAdded(item);
}
}
return item;
}
return addNewToLastBlock(msg, type);
}
void ChannelHistory::cleared(bool leaveItems) {
_joinedMessage = nullptr;
}
@@ -678,7 +658,7 @@ void checkForSwitchInlineButton(HistoryItem *item) {
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
for_const (auto &row, markup->rows) {
for_const (auto &button, row) {
if (button.type == HistoryMessageReplyMarkup::Button::Type::SwitchInline) {
if (button.type == HistoryMessageMarkupButton::Type::SwitchInline) {
Notify::switchInlineBotButtonReceived(QString::fromUtf8(button.data));
return;
}
@@ -795,12 +775,7 @@ void Histories::checkSelfDestructItems() {
}
HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) {
auto msgId = MsgId(0);
switch (msg.type()) {
case mtpc_messageEmpty: msgId = msg.c_messageEmpty().vid.v; break;
case mtpc_message: msgId = msg.c_message().vid.v; break;
case mtpc_messageService: msgId = msg.c_messageService().vid.v; break;
}
const auto msgId = idFromMessage(msg);
if (!msgId) return nullptr;
auto result = App::histItemById(channelId(), msgId);
@@ -809,7 +784,10 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
result->detach();
}
if (msg.type() == mtpc_message) {
result->updateMedia(msg.c_message().has_media() ? (&msg.c_message().vmedia) : 0);
const auto media = msg.c_message().has_media()
? &msg.c_message().vmedia
: nullptr;
result->updateMedia(media);
if (applyServiceAction) {
App::checkSavedGif(result);
}
@@ -1093,72 +1071,85 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
return result;
}
HistoryItem *History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg) {
not_null<HistoryItem*> History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg) {
return HistoryMessage::create(this, id, flags, date, from, postAuthor, msg);
}
HistoryItem *History::createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
not_null<HistoryItem*> History::createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, doc, caption, markup);
}
HistoryItem *History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
not_null<HistoryItem*> History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, photo, caption, markup);
}
HistoryItem *History::createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
not_null<HistoryItem*> History::createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, game, markup);
}
HistoryItem *History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) {
not_null<HistoryItem*> History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) {
auto message = HistoryService::PreparedText { text };
return addNewItem(HistoryService::create(this, msgId, date, message, flags), newMsg);
}
HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type) {
if (isChannel()) return asChannelHistory()->addNewChannelMessage(msg, type);
if (type == NewMessageExisting) return addToHistory(msg);
if (type == NewMessageExisting) {
return addToHistory(msg);
}
if (!loadedAtBottom() || peer->migrateTo()) {
HistoryItem *item = addToHistory(msg);
if (item) {
if (const auto item = addToHistory(msg)) {
setLastMessage(item);
if (type == NewMessageUnread) {
newItemAdded(item);
}
return item;
}
return item;
return nullptr;
}
return addNewToLastBlock(msg, type);
}
HistoryItem *History::addNewToLastBlock(const MTPMessage &msg, NewMessageType type) {
auto applyServiceAction = (type == NewMessageUnread);
auto detachExistingItem = (type != NewMessageLast);
auto item = createItem(msg, applyServiceAction, detachExistingItem);
Expects(type != NewMessageExisting);
const auto applyServiceAction = (type == NewMessageUnread);
const auto detachExistingItem = (type != NewMessageLast);
const auto item = createItem(msg, applyServiceAction, detachExistingItem);
if (!item || !item->detached()) {
return item;
}
return addNewItem(item, (type == NewMessageUnread));
const auto result = addNewItem(item, (type == NewMessageUnread));
if (type == NewMessageLast) {
// When we add just one last item, like we do while loading dialogs,
// we want to remove a single added grouped media, otherwise it will
// jump once we open the message history (first we show only that
// media, then we load the rest of the group and show the group).
//
// That way when we open the message history we show nothing until a
// whole history part is loaded, it certainly will contain the group.
removeOrphanMediaGroupPart();
}
return result;
}
HistoryItem *History::addToHistory(const MTPMessage &msg) {
return createItem(msg, false, false);
}
HistoryItem *History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item) {
not_null<HistoryItem*> History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item) {
return addNewItem(createItemForwarded(id, flags, date, from, postAuthor, item), true);
}
HistoryItem *History::addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
not_null<HistoryItem*> History::addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
return addNewItem(createItemDocument(id, flags, viaBotId, replyTo, date, from, postAuthor, doc, caption, markup), true);
}
HistoryItem *History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
not_null<HistoryItem*> History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
return addNewItem(createItemPhoto(id, flags, viaBotId, replyTo, date, from, postAuthor, photo, caption, markup), true);
}
HistoryItem *History::addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
not_null<HistoryItem*> History::addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
return addNewItem(createItemGame(id, flags, viaBotId, replyTo, date, from, postAuthor, game, markup), true);
}
@@ -1172,15 +1163,17 @@ void History::setUnreadMentionsCount(int count) {
bool History::addToUnreadMentions(
MsgId msgId,
AddToUnreadMentionsMethod method) {
auto allLoaded = _unreadMentionsCount ? (_unreadMentions.size() >= *_unreadMentionsCount) : false;
UnreadMentionType type) {
auto allLoaded = _unreadMentionsCount
? (_unreadMentions.size() >= *_unreadMentionsCount)
: false;
if (allLoaded) {
if (method == AddToUnreadMentionsMethod::New) {
if (type == UnreadMentionType::New) {
++*_unreadMentionsCount;
_unreadMentions.insert(msgId);
return true;
}
} else if (!_unreadMentions.empty() && method != AddToUnreadMentionsMethod::New) {
} else if (!_unreadMentions.empty() && type != UnreadMentionType::New) {
_unreadMentions.insert(msgId);
return true;
}
@@ -1250,32 +1243,24 @@ void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) {
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged);
}
HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
not_null<HistoryItem*> History::addNewItem(not_null<HistoryItem*> adding, bool newMsg) {
Expects(!isBuildingFrontBlock());
addItemToBlock(adding);
setLastMessage(adding);
if (newMsg) {
newItemAdded(adding);
const auto [groupFrom, groupTill] = recountGroupingFromTill(adding);
if (groupFrom != groupTill || groupFrom->groupId()) {
recountGrouping(groupFrom, groupTill);
}
adding->addToUnreadMentions(AddToUnreadMentionsMethod::New);
if (IsServerMsgId(adding->id)) {
if (auto sharedMediaTypes = adding->sharedMediaTypes()) {
if (newMsg) {
Auth().storage().add(Storage::SharedMediaAddNew(
peer->id,
sharedMediaTypes,
adding->id));
} else {
auto from = loadedAtTop() ? 0 : minMsgId();
auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
Auth().storage().add(Storage::SharedMediaAddExisting(
peer->id,
sharedMediaTypes,
adding->id,
{ from, till }));
}
if (!newMsg && IsServerMsgId(adding->id)) {
if (const auto sharedMediaTypes = adding->sharedMediaTypes()) {
auto from = loadedAtTop() ? 0 : minMsgId();
auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
Auth().storage().add(Storage::SharedMediaAddExisting(
peer->id,
sharedMediaTypes,
adding->id,
{ from, till }));
}
}
if (adding->from()->id) {
@@ -1358,6 +1343,11 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
}
}
setLastMessage(adding);
if (newMsg) {
newItemAdded(adding);
}
return adding;
}
@@ -1378,8 +1368,9 @@ void History::clearSendAction(not_null<UserData*> from) {
}
}
void History::newItemAdded(HistoryItem *item) {
void History::newItemAdded(not_null<HistoryItem*> item) {
App::checkImageCacheSize();
item->indexAsNewItem();
if (const auto from = item->from() ? item->from()->asUser() : nullptr) {
if (from == item->author()) {
clearSendAction(from);
@@ -1388,7 +1379,9 @@ void History::newItemAdded(HistoryItem *item) {
from->madeAction(itemServerTime.v);
}
if (item->out()) {
if (unreadBar) unreadBar->destroyUnreadBar();
if (unreadBar) {
unreadBar->destroyUnreadBar();
}
if (!item->unread()) {
outboxRead(item);
}
@@ -1433,8 +1426,7 @@ HistoryBlock *History::prepareBlockForAddingItem() {
return result;
};
void History::addItemToBlock(HistoryItem *item) {
Expects(item != nullptr);
void History::addItemToBlock(not_null<HistoryItem*> item) {
Expects(item->detached());
auto block = prepareBlockForAddingItem();
@@ -1499,7 +1491,7 @@ void History::addToSharedMedia(std::vector<MsgId> (&medias)[kSharedMediaTypeCoun
}
value.push_back(indices.join(","));
}
SignalHandlers::setCrashAnnotation("full", value.join(";"));
CrashReports::SetAnnotation("full", value.join(";"));
Assert(!"History desync caught!");
//// Logging
@@ -1527,6 +1519,9 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
return;
}
auto firstAdded = (HistoryItem*)nullptr;
auto lastAdded = (HistoryItem*)nullptr;
auto logged = QStringList();
logged.push_back(QString::number(minMsgId()));
logged.push_back(QString::number(maxMsgId()));
@@ -1538,9 +1533,12 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
--i;
auto adding = createItem(*i, false, true);
const auto adding = createItem(*i, false, true);
if (!adding) continue;
if (!firstAdded) firstAdded = adding;
lastAdded = adding;
if (minAdded < 0 || minAdded > adding->id) {
minAdded = adding->id;
}
@@ -1573,7 +1571,7 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
}
for (auto i = block->items.size(); i > 0; --i) {
auto item = block->items[i - 1];
item->addToUnreadMentions(AddToUnreadMentionsMethod::Front);
item->addToUnreadMentions(UnreadMentionType::Existing);
if (item->from()->id) {
if (lastAuthors) { // chats
if (auto user = item->from()->asUser()) {
@@ -1631,11 +1629,16 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
logged.push_back(QString::number(minAdded));
logged.push_back(QString::number(maxAdded));
SignalHandlers::setCrashAnnotation("old_minmaxwas_minmaxadd", logged.join(";"));
CrashReports::SetAnnotation("old_minmaxwas_minmaxadd", logged.join(";"));
addBlockToSharedMedia(block);
SignalHandlers::setCrashAnnotation("old_minmaxwas_minmaxadd", "");
CrashReports::ClearAnnotation("old_minmaxwas_minmaxadd");
if (lastAdded) {
const auto [from, till] = recountGroupingFromTill(lastAdded);
recountGrouping(firstAdded, till);
}
if (isChannel()) {
asChannelHistory()->checkJoinedMessage();
@@ -1654,6 +1657,9 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
}
}
auto firstAdded = (HistoryItem*)nullptr;
auto lastAdded = (HistoryItem*)nullptr;
Assert(!isBuildingFrontBlock());
if (!slice.isEmpty()) {
auto logged = QStringList();
@@ -1664,12 +1670,14 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
auto maxAdded = -1;
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
auto atLeastOneAdded = false;
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
--i;
auto adding = createItem(*i, false, true);
const auto adding = createItem(*i, false, true);
if (!adding) continue;
if (!firstAdded) firstAdded = adding;
lastAdded = adding;
if (minAdded < 0 || minAdded > adding->id) {
minAdded = adding->id;
}
@@ -1678,7 +1686,6 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
}
addItemToBlock(adding);
atLeastOneAdded = true;
if (auto types = adding->sharedMediaTypes()) {
for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
auto type = static_cast<Storage::SharedMediaType>(i);
@@ -1693,21 +1700,26 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
}
logged.push_back(QString::number(minAdded));
logged.push_back(QString::number(maxAdded));
SignalHandlers::setCrashAnnotation("new_minmaxwas_minmaxadd", logged.join(";"));
CrashReports::SetAnnotation("new_minmaxwas_minmaxadd", logged.join(";"));
if (!atLeastOneAdded) {
if (!firstAdded) {
newLoaded = true;
setLastMessage(lastAvailableMessage());
}
addToSharedMedia(medias, wasLoadedAtBottom != loadedAtBottom());
SignalHandlers::setCrashAnnotation("new_minmaxwas_minmaxadd", "");
CrashReports::ClearAnnotation("new_minmaxwas_minmaxadd");
}
if (!wasLoadedAtBottom) {
checkAddAllToUnreadMentions();
}
if (firstAdded) {
const auto [from, till] = recountGroupingFromTill(firstAdded);
recountGrouping(from, lastAdded);
}
if (isChannel()) asChannelHistory()->checkJoinedMessage();
checkLastMsg();
}
@@ -1728,9 +1740,9 @@ void History::checkAddAllToUnreadMentions() {
return;
}
for_const (auto block, blocks) {
for_const (auto item, block->items) {
item->addToUnreadMentions(AddToUnreadMentionsMethod::Back);
for (const auto block : blocks) {
for (const auto item : block->items) {
item->addToUnreadMentions(UnreadMentionType::Existing);
}
}
}
@@ -2006,7 +2018,7 @@ void History::destroyUnreadBar() {
}
}
HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex) {
not_null<HistoryItem*> History::addNewInTheMiddle(not_null<HistoryItem*> newItem, int32 blockIndex, int32 itemIndex) {
Expects(blockIndex >= 0);
Expects(blockIndex < blocks.size());
Expects(itemIndex >= 0);
@@ -2028,9 +2040,135 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex,
newItem->nextItemChanged();
}
const auto [groupFrom, groupTill] = recountGroupingFromTill(newItem);
if (groupFrom != groupTill || groupFrom->groupId()) {
recountGrouping(groupFrom, groupTill);
}
return newItem;
}
HistoryItem *History::findNextItem(not_null<HistoryItem*> item) const {
Expects(!item->detached());
const auto nextBlockIndex = item->block()->indexInHistory() + 1;
const auto nextItemIndex = item->indexInBlock() + 1;
if (nextItemIndex < int(item->block()->items.size())) {
return item->block()->items[nextItemIndex];
} else if (nextBlockIndex < int(blocks.size())) {
return blocks[nextBlockIndex]->items.front();
}
return nullptr;
}
HistoryItem *History::findPreviousItem(not_null<HistoryItem*> item) const {
Expects(!item->detached());
const auto blockIndex = item->block()->indexInHistory();
const auto itemIndex = item->indexInBlock();
if (itemIndex > 0) {
return item->block()->items[itemIndex - 1];
} else if (blockIndex > 0) {
return blocks[blockIndex - 1]->items.back();
}
return nullptr;
}
not_null<HistoryItem*> History::findGroupFirst(
not_null<HistoryItem*> item) const {
const auto group = item->Get<HistoryMessageGroup>();
Assert(group != nullptr);
Assert(group->leader != nullptr);
const auto leaderGroup = (group->leader == item)
? group
: group->leader->Get<HistoryMessageGroup>();
Assert(leaderGroup != nullptr);
return leaderGroup->others.empty()
? group->leader
: leaderGroup->others.front().get();
}
not_null<HistoryItem*> History::findGroupLast(
not_null<HistoryItem*> item) const {
const auto group = item->Get<HistoryMessageGroup>();
Assert(group != nullptr);
return group->leader;
}
void History::recountGroupingAround(not_null<HistoryItem*> item) {
Expects(item->history() == this);
if (!item->detached() && item->groupId()) {
const auto [groupFrom, groupTill] = recountGroupingFromTill(item);
recountGrouping(groupFrom, groupTill);
}
}
auto History::recountGroupingFromTill(not_null<HistoryItem*> item)
-> std::pair<not_null<HistoryItem*>, not_null<HistoryItem*>> {
const auto recountFromItem = [&] {
if (const auto prev = findPreviousItem(item)) {
if (prev->groupId()) {
return findGroupFirst(prev);
}
}
return item;
}();
if (recountFromItem == item && !item->groupId()) {
return { item, item };
}
const auto recountTillItem = [&] {
if (const auto next = findNextItem(item)) {
if (next->groupId()) {
return findGroupLast(next);
}
}
return item;
}();
return { recountFromItem, recountTillItem };
}
void History::recountGrouping(
not_null<HistoryItem*> from,
not_null<HistoryItem*> till) {
Expects(!from->detached());
Expects(!till->detached());
from->validateGroupId();
auto others = std::vector<not_null<HistoryItem*>>();
auto currentGroupId = from->groupId();
auto prev = from;
while (prev != till) {
auto item = findNextItem(prev);
item->validateGroupId();
const auto groupId = item->groupId();
if (currentGroupId) {
if (groupId == currentGroupId) {
others.push_back(prev);
} else {
for (const auto other : others) {
other->makeGroupMember(prev);
}
prev->makeGroupLeader(base::take(others));
currentGroupId = groupId;
}
} else if (groupId) {
currentGroupId = groupId;
}
prev = item;
}
if (currentGroupId) {
for (const auto other : others) {
other->makeGroupMember(prev);
}
till->makeGroupLeader(base::take(others));
}
}
void History::startBuildingFrontBlock(int expectedItemsCount) {
Assert(!isBuildingFrontBlock());
Assert(expectedItemsCount > 0);
@@ -2251,11 +2389,11 @@ int History::resizeGetHeight(int newWidth) {
}
ChannelHistory *History::asChannelHistory() {
return isChannel() ? static_cast<ChannelHistory*>(this) : 0;
return isChannel() ? static_cast<ChannelHistory*>(this) : nullptr;
}
const ChannelHistory *History::asChannelHistory() const {
return isChannel() ? static_cast<const ChannelHistory*>(this) : 0;
return isChannel() ? static_cast<const ChannelHistory*>(this) : nullptr;
}
not_null<History*> History::migrateToOrMe() const {
@@ -2277,6 +2415,25 @@ bool History::isDisplayedEmpty() const {
return isEmpty() || ((blocks.size() == 1) && blocks.front()->items.size() == 1 && blocks.front()->items.front()->isEmpty());
}
bool History::hasOrphanMediaGroupPart() const {
if (loadedAtTop() || !loadedAtBottom()) {
return false;
} else if (blocks.size() != 1) {
return false;
} else if (blocks.front()->items.size() != 1) {
return false;
}
return blocks.front()->items.front()->groupId() != MessageGroupId();
}
bool History::removeOrphanMediaGroupPart() {
if (hasOrphanMediaGroupPart()) {
clear(true);
return true;
}
return false;
}
void History::clear(bool leaveItems) {
if (unreadBar) {
unreadBar = nullptr;
@@ -2470,7 +2627,7 @@ void History::setPinnedIndex(int pinnedIndex) {
void History::changeMsgId(MsgId oldId, MsgId newId) {
}
void History::removeBlock(HistoryBlock *block) {
void History::removeBlock(not_null<HistoryBlock*> block) {
Expects(block->items.empty());
if (_buildingFrontBlock && block == _buildingFrontBlock->block) {
@@ -2521,9 +2678,21 @@ void HistoryBlock::clear(bool leaveItems) {
}
}
void HistoryBlock::removeItem(HistoryItem *item) {
void HistoryBlock::removeItem(not_null<HistoryItem*> item) {
Expects(item->block() == this);
auto [groupFrom, groupTill] = _history->recountGroupingFromTill(item);
const auto groupHistory = _history;
const auto needGroupRecount = (groupFrom != groupTill);
if (needGroupRecount) {
if (groupFrom == item) {
groupFrom = groupHistory->findNextItem(groupFrom);
}
if (groupTill == item) {
groupTill = groupHistory->findPreviousItem(groupTill);
}
}
auto blockIndex = indexInHistory();
auto itemIndex = item->indexInBlock();
if (_history->showFrom == item) {
@@ -2557,4 +2726,8 @@ void HistoryBlock::removeItem(HistoryItem *item) {
if (items.empty()) {
delete this;
}
if (needGroupRecount) {
groupHistory->recountGrouping(groupFrom, groupTill);
}
}

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*>>;
@@ -136,6 +134,7 @@ enum HistoryMediaType {
MediaTypeVoiceFile,
MediaTypeGame,
MediaTypeInvoice,
MediaTypeGrouped,
MediaTypeCount
};
@@ -172,10 +171,9 @@ struct Draft;
class HistoryMedia;
class HistoryMessage;
enum class AddToUnreadMentionsMethod {
enum class UnreadMentionType {
New, // when new message is added to history
Front, // when old messages slice was received
Back, // when new messages slice was received and it is the last one, we index all media
Existing, // when some messages slice was received
};
namespace Dialogs {
@@ -209,6 +207,8 @@ public:
return blocks.empty();
}
bool isDisplayedEmpty() const;
bool hasOrphanMediaGroupPart() const;
bool removeOrphanMediaGroupPart();
void clear(bool leaveItems = false);
void clearUpTill(MsgId availableMinId);
@@ -217,13 +217,13 @@ public:
virtual ~History();
HistoryItem *addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type);
HistoryItem *addToHistory(const MTPMessage &msg);
HistoryItem *addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
HistoryItem *addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
HistoryItem *addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
HistoryItem *addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
not_null<HistoryItem*> addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
not_null<HistoryItem*> addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
not_null<HistoryItem*> addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
not_null<HistoryItem*> addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
not_null<HistoryItem*> addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
// Used only internally and for channel admin log.
HistoryItem *createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem);
@@ -231,7 +231,7 @@ public:
void addOlderSlice(const QVector<MTPMessage> &slice);
void addNewerSlice(const QVector<MTPMessage> &slice);
void newItemAdded(HistoryItem *item);
void newItemAdded(not_null<HistoryItem*> item);
int countUnread(MsgId upTo);
void updateShowFrom();
@@ -366,7 +366,7 @@ public:
return (getUnreadMentionsCount() > 0);
}
void setUnreadMentionsCount(int count);
bool addToUnreadMentions(MsgId msgId, AddToUnreadMentionsMethod method);
bool addToUnreadMentions(MsgId msgId, UnreadMentionType type);
void eraseFromUnreadMentions(MsgId msgId);
void addUnreadMentionsSlice(const MTPmessages_Messages &result);
@@ -419,6 +419,7 @@ public:
}
HistoryItemsList validateForwardDraft();
void setForwardDraft(MessageIdsList &&items);
void recountGroupingAround(not_null<HistoryItem*> item);
// some fields below are a property of a currently displayed instance of this
// conversation history not a property of the conversation history itself
@@ -475,17 +476,17 @@ protected:
// this method just removes a block from the blocks list
// when the last item from this block was detached and
// calls the required previousItemChanged()
void removeBlock(HistoryBlock *block);
void removeBlock(not_null<HistoryBlock*> block);
void clearBlocks(bool leaveItems);
HistoryItem *createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg);
HistoryItem *createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
HistoryItem *createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
not_null<HistoryItem*> createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg);
not_null<HistoryItem*> createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
not_null<HistoryItem*> createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
not_null<HistoryItem*> createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
HistoryItem *addNewItem(HistoryItem *adding, bool newMsg);
HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex);
not_null<HistoryItem*> addNewItem(not_null<HistoryItem*> adding, bool newMsg);
not_null<HistoryItem*> addNewInTheMiddle(not_null<HistoryItem*> newItem, int32 blockIndex, int32 itemIndex);
// All this methods add a new item to the first or last block
// depending on if we are in isBuildingFronBlock() state.
@@ -493,7 +494,7 @@ protected:
// Adds the item to the back or front block, depending on
// isBuildingFrontBlock(), creating the block if necessary.
void addItemToBlock(HistoryItem *item);
void addItemToBlock(not_null<HistoryItem*> item);
// Usually all new items are added to the last block.
// Only when we scroll up and add a new slice to the
@@ -517,6 +518,18 @@ private:
void clearSendAction(not_null<UserData*> from);
HistoryItem *findPreviousItem(not_null<HistoryItem*> item) const;
HistoryItem *findNextItem(not_null<HistoryItem*> item) const;
not_null<HistoryItem*> findGroupFirst(
not_null<HistoryItem*> item) const;
not_null<HistoryItem*> findGroupLast(
not_null<HistoryItem*> item) const;
auto recountGroupingFromTill(not_null<HistoryItem*> item)
-> std::pair<not_null<HistoryItem*>, not_null<HistoryItem*>>;
void recountGrouping(
not_null<HistoryItem*> from,
not_null<HistoryItem*> till);
enum class Flag {
f_has_pending_resized_items = (1 << 0),
f_pending_resize = (1 << 1),
@@ -593,11 +606,8 @@ public:
private:
friend class History;
HistoryItem* addNewChannelMessage(const MTPMessage &msg, NewMessageType type);
HistoryItem *addNewToBlocks(const MTPMessage &msg, NewMessageType type);
void checkMaxReadMessageDate();
void cleared(bool leaveItems);
QDateTime _maxReadMessageDate;
@@ -624,7 +634,7 @@ public:
~HistoryBlock() {
clear();
}
void removeItem(HistoryItem *item);
void removeItem(not_null<HistoryItem*> item);
int resizeGetHeight(int newWidth, bool resizeAllItems);
int y() const {

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);
@@ -461,3 +463,9 @@ historyFastShareIcon: icon {{ "fast_share", msgServiceFg, point(4px, 3px)}};
historyGoToOriginalIcon: icon {{ "title_back-flip_horizontal", msgServiceFg, point(8px, 7px) }};
historySavedFont: font(semibold 14px);
historyGroupWidthMax: maxMediaSize;
historyGroupWidthMin: minPhotoSize;
historyGroupSkip: 4px;
historyGroupRadialSize: 44px;
historyGroupRadialLine: 3px;

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

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