Compare commits

...

183 Commits

Author SHA1 Message Date
John Preston
37cf12f06e Beta version 4.5.9: Fix new window position. 2023-02-02 22:10:44 +04:00
John Preston
fa4b538e6f Beta version 4.5.9.
- Hide taskbar window preview when Telegram is locked by a passcode
(Windows only).
- More improvements for working with multiple windows.
- Bug fixes and other minor improvements.
2023-02-02 20:22:21 +04:00
John Preston
0495cf4187 Use better initial geometry for new windows. 2023-02-02 20:20:05 +04:00
Ilya Fedin
933f1944c7 Strip binaries in snap 2023-02-02 16:18:53 +04:00
Ilya Fedin
925849858b Use ninja generator for libjxl in snap 2023-02-02 16:18:53 +04:00
Ilya Fedin
3c3829d9c5 Replace some legacy variables in snapcraft.yml 2023-02-02 16:18:53 +04:00
John Preston
88f3aeca5b Show just logo in Dwm Preview when passcoded. 2023-02-02 16:14:36 +04:00
John Preston
c7b3c95dc6 Fix root view pinned bar glitch in comments. 2023-02-02 16:12:44 +04:00
John Preston
e950130da6 Fix date and forward bar overlapping. 2023-02-02 10:55:04 +04:00
John Preston
a4cdd83816 Improve crash report window on Windows. 2023-02-02 10:55:04 +04:00
John Preston
e10964a0bc Allow opening new account in a separate window. 2023-02-02 10:55:04 +04:00
John Preston
42a2f53a11 Fix wrong media controls activations.
Fixes #25778, fixes #25786.
2023-02-02 10:55:04 +04:00
John Preston
f713585f17 Remove non-authed account only without a window. 2023-02-02 10:55:04 +04:00
John Preston
5278ed1f41 Fix account activation after window closing. 2023-02-02 10:55:04 +04:00
John Preston
58cedb796e Fix possible crash on empty options from the server. 2023-02-02 10:55:04 +04:00
Ilya Fedin
de11987312 Fix changelog dates since new year 2023-01-31 13:52:48 +04:00
Ilya Fedin
204cfaa8ca Update patches commit in Dockerfile 2023-01-31 13:52:48 +04:00
Ilya Fedin
5d20d585b3 Don't use alcGetEnumValue for non-ALC enum value
This makes no effective change for openal-soft as alGetEnumValue and alcGetEnumValue do lookup in the same table, but it's more semantically right and openal-soft is not the only implementation of the API
2023-01-26 15:56:01 +04:00
Ilya Fedin
2be4641496 Install launcher on every launch on Linux
Just like AppUserModelId on Windows

This makes the cheat code and having the function outside of private namespace unnecessary
2023-01-23 12:16:59 +04:00
Ilya Fedin
b62e1d5036 Set prgname and application name before glibmm initialization
This ensures possible warnings use right process name
2023-01-22 15:11:02 +04:00
Ilya Fedin
fc8d1e21e8 Merge generate-caches and desktop-qt snap parts 2023-01-22 10:43:03 +04:00
John Preston
8ddbfb7de5 Beta version 4.5.8: Fix build with GCC. 2023-01-22 00:13:13 +04:00
John Preston
89687e5bff Beta version 4.5.8.
- Allow opening another account in a new window
(see Settings > Advanced > Experimental Settings).
- A lot of bugfixes for working with more than one window.
2023-01-21 21:27:45 +04:00
John Preston
fcfacf1f9d Fix scroll in return to a channel I'm not in. 2023-01-21 21:24:54 +04:00
John Preston
a6484e6131 Fix build on MSVC. 2023-01-21 21:12:40 +04:00
John Preston
c2578f9a5a Add an #import statement in lib_webrtc. 2023-01-21 21:08:49 +04:00
John Preston
0de77a051a Fix interface scale padding in non-authed Settings. 2023-01-21 21:08:49 +04:00
John Preston
b0f8846d12 Fix opening topic on non-existent message error display. 2023-01-21 21:08:49 +04:00
23rd
6258aa01b8 Replaced multiple static constants for square root of 2 with Qt define. 2023-01-21 19:40:33 +03:00
Ilya Fedin
c8aa97b6b2 Fix fonts in snap
Regression was introduced in 09bd953c18
2023-01-21 09:44:14 +04:00
Ilya Fedin
d67c48fda1 Generate various caches in snap to speedup launch after updates 2023-01-21 09:44:14 +04:00
John Preston
7caabb8f5a Fix crash in topics disabling. 2023-01-21 09:42:57 +04:00
23rd
37454b4ff4 Added list of active group calls to Calls box. 2023-01-21 07:29:40 +04:00
23rd
cd032f5c16 Fixed update of flag for group calls from mtp chat data. 2023-01-21 07:29:40 +04:00
23rd
0c17bdc783 Replaced Calls box with GenericBox. 2023-01-21 07:29:40 +04:00
23rd
23a1f7b83c Fixed display of empty result of topics search. 2023-01-21 07:29:40 +04:00
23rd
465a33f095 Fixed display of loading peer list item in choose forum topic box. 2023-01-21 07:29:40 +04:00
Ilya Fedin
8820b9046d Map PipeWire's modules in snap 2023-01-20 16:53:30 +04:00
Ilya Fedin
09bd953c18 Add pipewire configs to snap 2023-01-20 14:44:09 +04:00
Ilya Fedin
6f89413c76 Don't get cursor position twice for open with menu 2023-01-20 12:58:48 +04:00
John Preston
a65e25b8ae Fix accidental lib_ui rollback. 2023-01-20 12:13:42 +04:00
Ilya Fedin
24ecd2ac88 Map PipeWire's SPA plugins in snap 2023-01-20 12:12:49 +04:00
John Preston
e8f27be364 Fix build without DBus integration. 2023-01-20 10:58:45 +04:00
John Preston
5e12cd27df Fix web_app_request_[theme|viewport].
Fixes #25752.
2023-01-20 09:35:18 +04:00
John Preston
7d1cc67019 Fix sending modified photos with existing bytes. 2023-01-19 13:05:38 +04:00
John Preston
507a064153 Fix OpenWith dropdown menu positioning on Windows. 2023-01-19 12:52:59 +04:00
John Preston
9751d36788 Fix child geometry for mega-rich popup menus. 2023-01-19 12:52:58 +04:00
John Preston
c46fd66abe Fix custom emoji position in sets. 2023-01-19 11:45:40 +04:00
John Preston
ec3957fcf3 Active round video moving to active window. 2023-01-19 11:42:00 +04:00
John Preston
b80b770631 Fix input field shortcuts on macOS. 2023-01-19 10:36:43 +04:00
John Preston
0f234188e1 Fix crash in SystemMediaControls init. 2023-01-19 09:57:33 +04:00
23rd
df5baba86b Moved out function for painting of color buttons to public interface. 2023-01-19 08:51:57 +03:00
23rd
64bd839d2c Slightly improved code style of color editor. 2023-01-19 08:51:57 +03:00
23rd
9390450049 Moved out color editor to td_ui. 2023-01-19 08:51:57 +03:00
23rd
e3334f7a87 Replaced EditColorBox with Ui::GenericBox. 2023-01-19 08:51:57 +03:00
23rd
58ed30d30e Moved constants of arc angles to td_ui. 2023-01-19 08:51:57 +03:00
John Preston
cdfdccbb66 Detach SystemMediaControls from Window::Controller. 2023-01-19 09:46:20 +04:00
John Preston
6b8f80bd63 Fix starting of video messages. 2023-01-19 09:46:20 +04:00
John Preston
6e5dfc79d4 Fix mouse BackButton with more than one window.
Fixes #24704.
2023-01-19 09:46:20 +04:00
John Preston
f8e76f1b84 Single entry point to search in chat. 2023-01-19 09:46:19 +04:00
John Preston
2c75fe033c Fix inline GIFs play start in separate windows.
Fixes #25694.
2023-01-19 09:46:19 +04:00
John Preston
b3667d69a1 Each MainWindow has its own Global Menu. 2023-01-19 09:46:19 +04:00
John Preston
ba520aadcb Fix hashtags in separate windows. 2023-01-19 09:46:19 +04:00
John Preston
e4c16ccba4 Create tray icon without a parent. 2023-01-19 09:46:19 +04:00
John Preston
3e332ad8e7 Rewrite shortcuts using QAction.
That way they don't depend on the main window.
2023-01-19 09:46:19 +04:00
John Preston
5154fe0044 Fix call bar appearance in a new MainWidget. 2023-01-19 09:46:19 +04:00
John Preston
dcb1315d53 Fix assertion violation in a new window init.
Fixes #24620.

In MainWidget updateControlsGeometry accesses SessionController
and itself through SessionController::content, which is not filled
in the MainWidget constructor yet, so ignore initial updates.
2023-01-19 09:46:19 +04:00
John Preston
7023b013ce Initial support of separate windows for accounts. 2023-01-19 09:45:28 +04:00
John Preston
86ed2745e3 Fix new forum userpic rounding. 2023-01-19 09:42:14 +04:00
Ilya Fedin
7db2acc742 Set UTF-8 code page in Windows manifest 2023-01-19 08:34:46 +04:00
Ilya Fedin
745b01a407 Another attempt to enable UTF-8 charset on Windows 2023-01-19 08:34:46 +04:00
Ilya Fedin
95979b1ad9 Call scheme handler registration after opening main window
To avoid system dialog being under it
2023-01-19 08:33:45 +04:00
John Preston
5910efa0bd Beta version 4.5.7.
- Fix glitches after moving window to another screen (Windows only).
2023-01-13 13:17:39 +04:00
John Preston
037e8f1858 Allow window to receive WM_DPICHANGED in Qt.
Fixes #25726.
2023-01-13 12:46:20 +04:00
John Preston
b1d1d73541 Copy/Paste original JPEG bytes to clipboard. 2023-01-13 12:46:17 +04:00
Ilya Fedin
9b154b3c91 Ensure freedesktop capabilities aren't used when GNotification is used 2023-01-13 10:24:06 +04:00
Ilya Fedin
a1f9b5a96f Get GApplication out of experimental settings
GApplication will always be used on Linux now. GNotification gets a toggle instead.
2023-01-12 21:49:16 +04:00
John Preston
2887c0b564 Beta version 4.5.6.
- Try enabling non-fractional scale
High DPI support on Windows and Linux.
- Experimental setting for fractional scale
High DPI support on Windows and Linux.
- Fix navigation to bottom problems in groups you didn't join.
- Fix a crash in chat export settings changes.
- Fix a crash in sending some of JPEG images.
- Fix CJK fonts on Windows.
2023-01-12 21:35:44 +04:00
Ilya Fedin
9b7826ea0d Get GApplication out of experimental settings
GApplication will always be used on Linux now. GNotification gets a toggle instead.
2023-01-12 21:30:45 +04:00
Ilya Fedin
241be89e5c Enable the new experimental setting for Linux 2023-01-12 21:17:08 +04:00
John Preston
39075538fb Enable RoundPreferFloor HighDpi support on Windows.
Add an experimental setting for exact HighDPI on Windows.
2023-01-12 13:57:09 +04:00
John Preston
1592f70a7c Respect trackUnreadMessages in history jumps.
Fixes #25384.
2023-01-12 12:23:29 +04:00
John Preston
732bb25666 Fix read from clipboard after bot install. 2023-01-12 12:23:28 +04:00
John Preston
5cba1cdc64 Fix blurred background for small images.
Fixes #25707.
2023-01-12 11:45:45 +04:00
John Preston
d346925b9d Fix window title update on accounts switch. 2023-01-12 11:36:00 +04:00
John Preston
02f3985125 Inspect correct url() instead of dragText().
Fixes #25720.

Regression was introduced in 23387d6625.
2023-01-12 11:34:16 +04:00
John Preston
f3db43abc9 Fix sending compressed images. 2023-01-12 11:34:08 +04:00
John Preston
ecf61712cd Fix CJK font fallback on Windows.
Fixes #25714.
2023-01-12 11:34:02 +04:00
John Preston
b47c66155d Beta version 4.5.5.
- Fix crash in Settings.
2023-01-10 22:16:24 +04:00
John Preston
2fda96a375 Fix a crash in non-authorized Settings. 2023-01-10 22:10:58 +04:00
John Preston
12c2e42917 Beta version 4.5.4: Fix build with GCC. 2023-01-10 16:59:05 +04:00
John Preston
94a956ce19 Beta version 4.5.4: Fix build for Windows x64. 2023-01-10 16:47:57 +04:00
John Preston
704f64a0c9 Fix "Limit special config request types." 2023-01-10 16:47:41 +04:00
John Preston
f9ca7f4505 Beta version 4.5.4: Fix "Mark-as-read-inactive setting." 2023-01-10 16:24:27 +04:00
John Preston
0a3d31a91f Beta version 4.5.4: Try updating OpenAL on macOS. 2023-01-10 15:34:33 +04:00
John Preston
3c17fab15a Beta version 4.5.4: Mark-as-read-inactive setting.
In case an experimental setting of auto-scrolling is enabled,
just ignore the window activity check in marking chat as read..
2023-01-10 15:31:15 +04:00
John Preston
2efe409c60 Beta version 4.5.4.
- Allow wide range of interface scale options.
- Show opened chat name in the window title.
- Bug fixes and other minor improvements.
- Fix updating on macOS older than 10.14.
2023-01-10 15:23:19 +04:00
John Preston
1176421bf2 Limit special config request types. 2023-01-10 15:09:49 +04:00
John Preston
05911a7172 Fix initial scale counting from dpi. 2023-01-10 09:51:12 +04:00
Ilya Fedin
1326359745 Don't involve locale in filename timestamp generation
It was batch replaced as part of 7b5781b845, but it's not really semantically valid
2023-01-10 09:50:02 +04:00
John Preston
fc26457218 Show current chat name in the window title. 2023-01-09 18:39:07 +04:00
Ilya Fedin
173108a9cb Differ file download failure reasons
..and uncomment the code for handling incorrect permissions
2023-01-09 17:02:11 +04:00
Ilya Fedin
7307f0b1a5 Use temp directory for downloads in Linux sandbox by default 2023-01-09 16:09:57 +04:00
John Preston
c49dac57b7 Don't use window title for IPC. 2023-01-09 16:08:34 +04:00
Ilya Fedin
6288da2f3d Remove socket errors from Qt adapters 2023-01-09 15:01:14 +04:00
Ilya Fedin
ce37c6ef08 Implement lossless jpeg progression 2023-01-09 15:00:11 +04:00
John Preston
5f93725431 Relax controls hiding conditions in full screen.
Fixes #25499.
2023-01-09 14:03:27 +04:00
23rd
90dfdb0e1f Moved NeverFreedPointer to lib_base. 2023-01-09 12:51:32 +03:00
23rd
7cd330db9a Improved style of box for creation of linked chat. 2023-01-09 12:34:44 +03:00
23rd
b14ac5cafe Moved Info::Profile::FloatingIcon to td_ui. 2023-01-09 12:34:44 +03:00
John Preston
1fc929b78f Add some logs for jump-to-end bugs.
This is related to #25384.
2023-01-09 13:25:15 +04:00
John Preston
fd47fd4d9e Track shared media index better.
Fixes #25667.
2023-01-09 11:48:33 +04:00
John Preston
9b74958fab Fix send-as button appearance after joining the group. 2023-01-09 11:18:58 +04:00
John Preston
7091fb9448 Force weak linking of NaturalLanguage framework.
Fixes #25429.
2023-01-09 10:50:41 +04:00
John Preston
876cdcf26a Show "Photo set by you" in profile photos list. 2023-01-09 09:56:36 +04:00
John Preston
36eca970f2 Pass correct parent to choose folder file dialog.
I hope it fixes #25689, although it didn't work for me, not crash.
2023-01-09 09:44:58 +04:00
John Preston
21232e09a4 Fix a crash in topic from archive search.
Fixes #25609.
2023-01-09 09:36:20 +04:00
John Preston
2d9d373c7f Don't show "Report" for personal contact photos. 2023-01-09 09:20:36 +04:00
John Preston
23387d6625 Don't drag-n-drop "internal:" URLs. 2023-01-09 09:10:53 +04:00
John Preston
6137c64444 Fix scale preview on macOS. 2023-01-09 09:05:07 +04:00
John Preston
43a830f0af Fix in-window preview (Wayland / noCompositing). 2023-01-09 09:05:07 +04:00
John Preston
ff331c040a Allow huge range of interface scales. 2023-01-09 09:05:07 +04:00
John Preston
3532e187fd Write more DPI logs. 2023-01-09 09:05:07 +04:00
23rd
6467ba7739 Fixed painting of photo with spoiler in SendFilesBox on Retina. 2023-01-09 09:05:07 +04:00
23rd
8de3b2c0d3 Fixed painting of non personal photo in UserpicButton on Retina. 2023-01-09 09:05:07 +04:00
23rd
deeb022e0b Fixed possible crash on deleting own channel. 2023-01-09 09:05:07 +04:00
23rd
9e0e28dc45 Improved style of box for editing of linked chat. 2023-01-09 09:05:07 +04:00
23rd
c99ac0a264 Moved creation of divider with text and lottie to single place. 2023-01-09 09:05:07 +04:00
23rd
991fafb30e Fixed clickable online status in profile settings. 2023-01-09 09:05:06 +04:00
23rd
5cf5d4b4c4 Improved opening main menu from top left corner.
Fixed #17423.
2023-01-09 09:05:06 +04:00
23rd
38e42f9a95 Fixed ripple color of main menu button in narrow forum state. 2023-01-09 09:05:06 +04:00
23rd
9b7689993f Fixed phrase of global TTL for new chats. 2023-01-09 09:05:06 +04:00
23rd
0e3eddcb77 Slightly improved TTL badge in dialogs list. 2023-01-09 09:05:06 +04:00
23rd
3f829ef3b9 Accepted tg://login only for started domain. 2023-01-09 09:05:06 +04:00
23rd
de8d93ba73 Added support of multiline subtext for many usernames to info layer. 2023-01-09 09:05:06 +04:00
23rd
dad9f4b87d Flipped icon for rotation button in photo editor. 2023-01-09 09:05:06 +04:00
23rd
0f538e2606 Updated Qt to 5.15.8 on Windows. 2023-01-09 09:05:06 +04:00
John Preston
08fa6a9815 Version 4.5.3.
- Attempt to fix incoming video in calls from mobile apps.
2023-01-06 21:57:04 +04:00
John Preston
a7cf4027ea Attempt to fix calls incoming video. 2023-01-06 21:55:08 +04:00
Ilya Fedin
646c7ecceb Update Qt version for Linux in cmake 2023-01-06 12:57:54 +04:00
Ilya Fedin
3cbbe3d3c2 Update Qt to 6.4.2 on Linux 2023-01-05 23:00:13 +04:00
John Preston
0af26dd353 Capture mouse in PipeWire screen capture. 2023-01-05 10:09:44 +04:00
John Preston
159e366122 Choose screens/windows in Wayland screencapture.
Fixes #25674.
2023-01-05 09:55:46 +04:00
John Preston
b9081c26ba Use tg://settings/edit_profile instead /information. 2023-01-05 09:55:38 +04:00
John Preston
9933c6ba59 Mark topics as read using Ctrl+R shortcut.
Fixes #25669.
2023-01-05 09:53:51 +04:00
John Preston
eb0642f569 Version 4.5.2.
- Fix unread reactions button in private chats.
- Fix tile background saving after an app update.
- Allow Ctrl+6,7,8 to activate extra pinned chats.
2023-01-03 11:11:02 +04:00
John Preston
1cce35a5a5 Fix multiline checkbox geometry counting. 2023-01-03 11:06:40 +04:00
John Preston
aeb71e089a Fix tile background saving after an app update.
Fixes #25666, I hope fixes #16468, I hope fixes #5944.
2023-01-03 10:43:55 +04:00
John Preston
b962efeca3 Allow ctrl+6/7/8 to activate extra pinned chats.
Fixes #25647.
2023-01-03 09:59:42 +04:00
John Preston
eb6c350e72 Fix unread reactions button in chats with users.
Regression was introduced in 6a7f030ee7.

Fixes #25661.
2023-01-03 09:22:46 +04:00
John Preston
d496d41e7e Version 4.5.1: Fix excessive flood_wait trigger.
Regression was introduced in 1e8dfb7315.

Fixes #25494.
2023-01-02 17:33:39 +04:00
John Preston
19aa4f4acc Version 4.5.1.
- Fix crash in profile photo privacy edition.
- Allow sending photos larger than 1280px (in Experimental Settings).
2023-01-02 16:02:19 +04:00
John Preston
19350e3846 Open type="document" with photo as a photo.
Fixes #25600.
2023-01-02 15:08:36 +04:00
John Preston
741b524d71 Add description to an option (looks better). 2023-01-02 15:08:28 +04:00
John Preston
84288112fc Allow sending photos larger 1280 (experimental).
Improves #6520.
2023-01-02 14:26:41 +04:00
John Preston
7c537cd787 Revert "Removed downscaling of 2560px images before displaying them"
This reverts commit 0f3ec7893d.

Instead correct max limits of 2560x2560 will be used.
2023-01-02 14:26:41 +04:00
Ilya Fedin
c56977cbc1 Check autostart enabling success on Linux 2023-01-02 13:10:17 +04:00
John Preston
2afa2cd9ab Fix scroll reset bug in topics on message removal. 2023-01-02 12:26:20 +04:00
John Preston
442d0da5c1 Force autostart folder creation.
Also show an error if autostart couldn't be enabled.

Fixes #25608.
2023-01-02 12:26:20 +04:00
Ilya Fedin
db6bdf36af Update patches 2023-01-02 11:19:15 +04:00
Ilya Fedin
b246328dcf Use latest mesa in snap 2023-01-02 11:19:15 +04:00
John Preston
a27ea35edd Fix possible memory leak in jpeg inspecting. 2023-01-02 11:07:57 +04:00
John Preston
a7c4aea9ff Revert "Clear draft that failed to be saved."
This reverts commit 7866013ab6.

Loosing the current field text in case the server doesn't accept
the draft is worse than showing some sticked draft in the list.

We always can just hide the cloud draft in chats list in case you
can't edit it really if there are reports about them.
2023-01-02 10:50:59 +04:00
GitHub Action
1ba870a655 Update User-Agent for DNS to Chrome 108.0.5359.98. 2023-01-02 10:10:34 +04:00
GitHub Action
5bc3cf56fd Update copyright year to 2023. 2023-01-02 10:10:14 +04:00
John Preston
3c4cf2862b Fix crash in profile photo privacy edition.
Fixes #25645.
2023-01-02 10:09:31 +04:00
Daniel Novomeský
af69a7a01f Upgrade highway, libde265, libavif, libheif on Linux 2023-01-01 13:21:08 +04:00
Ilya Fedin
b9f7a501f5 Do pacman -Syu twice in prepare.py
So new databases are downloaded in case runtime updates and gets new repostiories
2023-01-01 13:19:11 +04:00
Ilya Fedin
322a085b70 Fix the check for Native Tools Command Prompt in prepare.py 2022-12-31 16:03:27 +04:00
Ilya Fedin
6c4dc34441 Fix build with various Windows locales 2022-12-31 16:02:28 +04:00
Ilya Fedin
efa287b786 Use text=True instead of decode() in prepare.py 2022-12-31 16:02:28 +04:00
John Preston
23e1c6128b Specify no non-exempt encryption usage in plist. 2022-12-30 17:30:18 +04:00
John Preston
bc71a2619a Version 4.5: Fix build with GCC. 2022-12-30 16:16:35 +04:00
John Preston
4f3510c47c Version 4.5: Fix search in topic. 2022-12-30 15:50:56 +04:00
John Preston
2adc20f07f Version 4.5.
- Media with spoiler effects.
You can wrap photos and videos you send in a fuzzy cover
by selecting media in the attachment menu
and tapping (...) > Hide With Spoiler.

- Setting pictures for your contacts.
You can choose your own picture for
a contact – only you will see it on their profile.

- Suggested profile pictures.
When editing your contacts, you can suggest
a photo for their profile. It will take them just two clicks
to add the picture you suggested.

- Public profile pictures.
If you only allow certain users to see your profile photos,
you can set a public picture for everyone else.

- Ultimate profile picture privacy.
You can set 'Who can see my profile photos' to 'Nobody'
and add some users or groups as exceptions.

- Member list privacy.
Owners of large groups can hide the list of members.
2022-12-30 14:55:09 +04:00
John Preston
b6ade7ce19 Fix spoiler / custom emoji in pinned bar unpause. 2022-12-30 14:27:00 +04:00
John Preston
cabed9587b Close PiP if message with video gets deleted. 2022-12-30 14:26:43 +04:00
John Preston
0ce01410a1 Fix crash in Pip-to-Viewer after message deletion.
Fixes #25262. Fixes #25522.
2022-12-30 14:18:04 +04:00
John Preston
d02819db13 Support spoilers in reply previews / pinned bar. 2022-12-30 14:06:20 +04:00
John Preston
46bae9ed74 Remove splits reverse. It was done on the server. 2022-12-30 10:49:51 +04:00
23rd
693ff3398e Fixed changing of button style between states within single intro step. 2022-12-29 23:25:53 +03:00
John Preston
567216f41f Fix crash in topic jump ripple animation.
Fixes #25500.
2022-12-29 17:48:33 +04:00
John Preston
1ef0791bc6 Fix OOM crash on wrong attached stickers hash.
Fixes #25495.
2022-12-29 17:11:24 +04:00
274 changed files with 5304 additions and 2460 deletions

View File

@@ -56,11 +56,11 @@ include(cmake/options.cmake)
if (NOT DESKTOP_APP_USE_PACKAGED)
if (WIN32)
set(qt_version 5.15.7)
set(qt_version 5.15.8)
elseif (APPLE)
set(qt_version 6.3.2)
else()
set(qt_version 6.4.1)
set(qt_version 6.4.2)
endif()
endif()
include(cmake/external/qt/package.cmake)

2
LEGAL
View File

@@ -1,7 +1,7 @@
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
Copyright (c) 2014-2022 The Telegram Desktop Authors.
Copyright (c) 2014-2023 The Telegram Desktop Authors.
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

View File

@@ -243,8 +243,6 @@ PRIVATE
boxes/download_path_box.h
boxes/edit_caption_box.cpp
boxes/edit_caption_box.h
boxes/edit_color_box.cpp
boxes/edit_color_box.h
boxes/edit_privacy_box.cpp
boxes/edit_privacy_box.h
boxes/gift_premium_box.cpp
@@ -841,8 +839,6 @@ PRIVATE
info/profile/info_profile_cover.h
info/profile/info_profile_emoji_status_panel.cpp
info/profile/info_profile_emoji_status_panel.h
info/profile/info_profile_icon.cpp
info/profile/info_profile_icon.h
info/profile/info_profile_inner_widget.cpp
info/profile/info_profile_inner_widget.h
info/profile/info_profile_members.cpp
@@ -1139,7 +1135,6 @@ PRIVATE
platform/platform_integration.h
platform/platform_main_window.h
platform/platform_notifications_manager.h
platform/platform_specific.cpp
platform/platform_specific.h
platform/platform_tray.h
platform/platform_window_title.h
@@ -1201,6 +1196,8 @@ PRIVATE
settings/settings_privacy_controllers.h
settings/settings_privacy_security.cpp
settings/settings_privacy_security.h
settings/settings_scale_preview.cpp
settings/settings_scale_preview.h
settings/settings_type.h
storage/details/storage_file_utilities.cpp
storage/details/storage_file_utilities.h

Binary file not shown.

View File

@@ -390,6 +390,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_no_chats_filter" = "No chats currently belong to this folder.";
"lng_contacts_loading" = "Loading...";
"lng_contacts_not_found" = "No contacts found";
"lng_topics_not_found" = "No topics found.";
"lng_dlg_search_for_messages" = "Search for messages";
"lng_update_telegram" = "Update Telegram";
"lng_dlg_search_in" = "Search messages in";
@@ -755,7 +756,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_download_path" = "Download path";
"lng_download_path_temp" = "Temp folder";
"lng_download_path_default" = "Default folder";
"lng_download_path_unset" = "Unset";
"lng_download_path_clear" = "Clear all";
"lng_download_path_header" = "Choose download path";
"lng_download_path_default_radio" = "Telegram folder in system «Downloads»";
@@ -763,7 +763,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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.\n\nThis might be because the download location you've selected is invalid. Try changing the \"Download path\" in Settings.";
"lng_download_path_failed" = "File download could not be started.\n\nThe default download location will be used now. You can always change it in Settings > Advanced > Download Path.\n\nPlease try once again.";
"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...";
@@ -1332,8 +1332,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_discussion_group_add" = "Add a group";
"lng_manage_linked_channel" = "Linked channel";
"lng_manage_linked_channel_restore" = "Restore linked channel";
"lng_manage_discussion_group_about" = "Select a group chat for discussion that will be displayed in your channel.";
"lng_manage_discussion_group_about_chosen" = "A link to {group} is shown to all subscribers in the bottom panel.";
"lng_manage_discussion_group_about" = "Select a group chat that will host comments from your channel.";
"lng_manage_discussion_group_about_chosen" = "{group} is selected as the group that hosts comments for your channel.";
"lng_manage_discussion_group_create" = "Create a new group";
"lng_manage_discussion_group_unlink" = "Unlink group";
"lng_manage_discussion_group_posted" = "Everything you post in the channel is forwarded to this group.";
@@ -1529,6 +1529,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_ttl_changed_you" = "You set messages to auto-delete in {duration}";
"lng_action_ttl_changed_channel" = "New messages will auto-delete in {duration}";
"lng_action_ttl_global" = "{from} uses a self-destruct timer for all chats. All new messages in this chat will be automatically deleted after {duration} they've been sent.";
"lng_action_ttl_global_me" = "You set a self-destruct timer for all chats. All new messages in this chat will be automatically deleted after {duration} theyve been sent.";
"lng_action_ttl_removed" = "{from} has set messages not to auto-delete";
"lng_action_ttl_removed_you" = "You disabled the auto-delete timer";
"lng_action_ttl_removed_channel" = "New messages will not auto-delete";
@@ -2498,6 +2499,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_mediaview_channel_photo" = "Channel Photo";
"lng_mediaview_profile_photo" = "Profile Photo";
"lng_mediaview_profile_public_photo" = "Public Photo";
"lng_mediaview_profile_photo_by_you" = "Photo set by you";
"lng_mediaview_file_n_of_amount" = "{file} {n} of {amount}";
"lng_mediaview_n_of_amount" = "Photo {n} of {amount}";
"lng_mediaview_doc_image" = "File";
@@ -2692,6 +2694,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_call_box_clear_all" = "Clear All";
"lng_call_box_clear_sure" = "Are you sure you want to completely clear your calls log?";
"lng_call_box_clear_button" = "Clear";
"lng_call_box_groupcalls_subtitle" = "Active video chats";
"lng_call_outgoing" = "Outgoing call";
"lng_call_video_outgoing" = "Outgoing video call";

View File

@@ -9,5 +9,6 @@
<file alias="cloud_password/hint.tgs">../../animations/cloud_password/hint.tgs</file>
<file alias="cloud_password/email.tgs">../../animations/cloud_password/email.tgs</file>
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
</qresource>
</RCC>

View File

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

View File

@@ -14,4 +14,9 @@
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>
</assembly>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,4,3,0
PRODUCTVERSION 4,4,3,0
FILEVERSION 4,5,9,0
PRODUCTVERSION 4,5,9,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "4.4.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
VALUE "FileVersion", "4.5.9.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "4.4.3.0"
VALUE "ProductVersion", "4.5.9.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,4,3,0
PRODUCTVERSION 4,4,3,0
FILEVERSION 4,5,9,0
PRODUCTVERSION 4,5,9,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "4.4.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
VALUE "FileVersion", "4.5.9.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "4.4.3.0"
VALUE "ProductVersion", "4.5.9.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -148,6 +148,7 @@ void EditMessageWithUploadedMedia(
MTPInputMedia media) {
const auto done = [=](Fn<void()> applyUpdates) {
if (item) {
item->removeFromSharedMediaIndex();
item->clearSavedMedia();
item->setIsLocalUpdateMedia(true);
applyUpdates();

View File

@@ -21,6 +21,9 @@ namespace Api {
template<typename Option>
[[nodiscard]] Data::SubscriptionOptions SubscriptionOptionsFromTL(
const QVector<Option> &tlOptions) {
if (tlOptions.isEmpty()) {
return {};
}
auto result = Data::SubscriptionOptions();
const auto monthlyAmount = [&] {
const auto &min = ranges::min_element(

View File

@@ -36,7 +36,7 @@ bool UnreadThings::trackMentions(Data::Thread *thread) const {
bool UnreadThings::trackReactions(Data::Thread *thread) const {
const auto peer = thread ? thread->peer().get() : nullptr;
return peer && (peer->isChat() || peer->isMegagroup());
return peer && (peer->isUser() || peer->isChat() || peer->isMegagroup());
}
void UnreadThings::preloadEnough(Data::Thread *thread) {

View File

@@ -123,22 +123,18 @@ using UpdatedFileReferences = Data::UpdatedFileReferences;
[[nodiscard]] std::shared_ptr<Window::Show> ShowForPeer(
not_null<PeerData*> peer) {
const auto separate = Core::App().separateWindowForPeer(peer);
const auto window = separate ? separate : Core::App().primaryWindow();
return std::make_shared<Window::Show>(window);
return std::make_shared<Window::Show>(Core::App().windowFor(peer));
}
void ShowChannelsLimitBox(not_null<PeerData*> peer) {
const auto primary = Core::App().primaryWindow();
if (!primary) {
return;
if (const auto window = Core::App().windowFor(peer)) {
window->invokeForSessionController(
&peer->session().account(),
peer,
[&](not_null<Window::SessionController*> controller) {
controller->show(Box(ChannelsLimitBox, &peer->session()));
});
}
primary->invokeForSessionController(
&peer->session().account(),
peer,
[&](not_null<Window::SessionController*> controller) {
controller->show(Box(ChannelsLimitBox, &peer->session()));
});
}
} // namespace
@@ -2137,7 +2133,6 @@ void ApiWrap::saveDraftsToCloud() {
if (const auto cloudDraft = history->cloudDraft(topicRootId)) {
if (cloudDraft->saveRequestId == requestId) {
history->clearCloudDraft(topicRootId);
history->applyCloudDraft(topicRootId);
}
}
const auto i = _draftsSaveRequestIds.find(weak);

View File

@@ -20,7 +20,7 @@ void showBox(
LayerOptions options,
anim::type animated) {
const auto window = Core::IsAppLaunched()
? Core::App().primaryWindow()
? Core::App().activePrimaryWindow()
: nullptr;
if (window) {
window->show(std::move(content), options, animated);
@@ -31,7 +31,7 @@ void showBox(
void hideLayer(anim::type animated) {
const auto window = Core::IsAppLaunched()
? Core::App().primaryWindow()
? Core::App().activePrimaryWindow()
: nullptr;
if (window) {
window->hideLayer(animated);
@@ -40,7 +40,7 @@ void hideLayer(anim::type animated) {
bool isLayerShown() {
const auto window = Core::IsAppLaunched()
? Core::App().primaryWindow()
? Core::App().activePrimaryWindow()
: nullptr;
return window && window->isLayerShown();
}

View File

@@ -69,10 +69,11 @@ void ChangeFilterById(
MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter),
MTP_int(filter.id()),
filter.tl()
)).done([=, chat = history->peer->name(), name = filter.title()]{
)).done([=, chat = history->peer->name(), name = filter.title()] {
// Since only the primary window has dialogs list,
// We can safely show toast there.
if (const auto controller = Core::App().primaryWindow()) {
const auto account = &history->session().account();
if (const auto controller = Core::App().windowFor(account)) {
auto text = (add
? tr::lng_filters_toast_add
: tr::lng_filters_toast_remove)(

View File

@@ -459,7 +459,7 @@ void ProxyRow::paintEvent(QPaintEvent *e) {
void ProxyRow::paintCheck(Painter &p) {
const auto loading = _progress
? _progress->computeState()
: Ui::RadialState{ 0., 0, FullArcLength };
: Ui::RadialState{ 0., 0, arc::kFullLength };
const auto toggled = _toggled.value(_view.selected ? 1. : 0.)
* (1. - loading.shown);
const auto _st = &st::defaultRadio;
@@ -484,7 +484,7 @@ void ProxyRow::paintCheck(Painter &p) {
_st->thickness,
pen.color(),
_st->bg);
} else if (loading.arcLength < FullArcLength) {
} else if (loading.arcLength < arc::kFullLength) {
p.drawArc(rect, loading.arcFrom, loading.arcLength);
} else {
p.drawEllipse(rect);

View File

@@ -147,7 +147,9 @@ void DownloadPathBox::setPathText(const QString &text) {
DownloadPathBox::Directory DownloadPathBox::typeFromPath(
const QString &path) {
if (path.isEmpty()) {
return Directory::Downloads;
return Core::App().canReadDefaultDownloadPath(true)
? Directory::Downloads
: Directory::Temp;
} else if (path == FileDialog::Tmp()) {
return Directory::Temp;
}

View File

@@ -78,14 +78,11 @@ auto ListFromMimeData(not_null<const QMimeData*> data, bool premium) {
: Ui::PreparedList(Error::EmptyFile, QString());
if (result.error == Error::None) {
return result;
} else if (data->hasImage()) {
auto image = qvariant_cast<QImage>(data->imageData());
if (!image.isNull()) {
return Storage::PrepareMediaFromImage(
std::move(image),
QByteArray(),
st::sendMediaPreviewSize);
}
} else if (auto read = Core::ReadMimeImage(data)) {
return Storage::PrepareMediaFromImage(
std::move(read.image),
std::move(read.content),
st::sendMediaPreviewSize);
}
return result;
}
@@ -451,7 +448,8 @@ void EditCaptionBox::setupPhotoEditorEventHandler() {
&file.information->media);
image->modifications = mods;
Storage::UpdateImageDetails(file, previewWidth);
const auto sideLimit = PhotoSideLimit();
Storage::UpdateImageDetails(file, previewWidth, sideLimit);
rebuildPreview();
};
const auto fileImage = std::make_shared<Image>(*large);

View File

@@ -590,11 +590,10 @@ void ChooseTopicSearchController::searchOnServer() {
}
delegate()->peerListSearchAddRow(topic->rootId().bare);
});
if (_offsetTopicId != savedTopicId) {
delegate()->peerListSearchRefreshRows();
} else {
if (_offsetTopicId == savedTopicId) {
_allLoaded = true;
}
delegate()->peerListSearchRefreshRows();
}).fail([=] {
_allLoaded = true;
}).send();
@@ -633,10 +632,13 @@ auto ChooseTopicBoxController::Row::generatePaintUserpicCallback(
int y,
int outerWidth,
int size) {
const auto &st = st::forumTopicRow;
x -= st.padding.left();
y -= st.padding.top();
auto view = Ui::PeerUserpicView();
p.translate(x, y);
_topic->paintUserpic(p, view, {
.st = &st::forumTopicRow,
.st = &st,
.currentBg = st::windowBg,
.now = crl::now(),
.width = outerWidth,
@@ -696,7 +698,7 @@ void ChooseTopicBoxController::rowClicked(not_null<PeerListRow*> row) {
void ChooseTopicBoxController::prepare() {
delegate()->peerListSetTitle(tr::lng_forward_choose());
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
setSearchNoResultsText(tr::lng_topics_not_found(tr::now));
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
refreshRows(true);

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "settings/settings_common.h"
#include "data/data_changes.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
@@ -25,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
#include "styles/style_settings.h"
namespace {
@@ -208,88 +210,24 @@ void Controller::choose(not_null<ChatData*> chat) {
Ui::LayerOption::KeepOther);
}
object_ptr<Ui::RpWidget> SetupAbout(
not_null<QWidget*> parent,
[[nodiscard]] rpl::producer<TextWithEntities> About(
not_null<ChannelData*> channel,
ChannelData *chat) {
auto about = object_ptr<Ui::FlatLabel>(
parent,
QString(),
st::linkedChatAbout);
about->setMarkedText([&] {
if (!channel->isBroadcast()) {
return tr::lng_manage_linked_channel_about(
tr::now,
lt_channel,
Ui::Text::Bold(chat->name()),
Ui::Text::WithEntities);
} else if (chat != nullptr) {
return tr::lng_manage_discussion_group_about_chosen(
tr::now,
lt_group,
Ui::Text::Bold(chat->name()),
Ui::Text::WithEntities);
}
return tr::lng_manage_discussion_group_about(
tr::now,
if (!channel->isBroadcast()) {
return tr::lng_manage_linked_channel_about(
lt_channel,
rpl::single(Ui::Text::Bold(chat->name())),
Ui::Text::WithEntities);
}());
return about;
} else if (chat != nullptr) {
return tr::lng_manage_discussion_group_about_chosen(
lt_group,
rpl::single(Ui::Text::Bold(chat->name())),
Ui::Text::WithEntities);
}
return tr::lng_manage_discussion_group_about(Ui::Text::WithEntities);
}
object_ptr<Ui::RpWidget> SetupFooter(
not_null<QWidget*> parent,
not_null<ChannelData*> channel) {
return object_ptr<Ui::FlatLabel>(
parent,
(channel->isBroadcast()
? tr::lng_manage_discussion_group_posted
: tr::lng_manage_linked_channel_posted)(),
st::linkedChatAbout);
}
object_ptr<Ui::RpWidget> SetupCreateGroup(
not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> navigation,
not_null<ChannelData*> channel,
Fn<void(ChannelData*)> callback) {
Expects(channel->isBroadcast());
auto result = object_ptr<Ui::SettingsButton>(
parent,
tr::lng_manage_discussion_group_create(
) | Ui::Text::ToUpper(),
st::infoCreateLinkedChatButton);
result->addClickHandler([=] {
const auto guarded = crl::guard(parent, callback);
Window::Show(navigation).showBox(
Box<GroupInfoBox>(
navigation,
GroupInfoBox::Type::Megagroup,
channel->name() + " Chat",
guarded),
Ui::LayerOption::KeepOther);
});
return result;
}
object_ptr<Ui::RpWidget> SetupUnlink(
not_null<QWidget*> parent,
not_null<ChannelData*> channel,
Fn<void(ChannelData*)> callback) {
auto result = object_ptr<Ui::SettingsButton>(
parent,
(channel->isBroadcast()
? tr::lng_manage_discussion_group_unlink
: tr::lng_manage_linked_channel_unlink)() | Ui::Text::ToUpper(),
st::infoUnlinkChatButton);
result->addClickHandler([=] {
callback(nullptr);
});
return result;
}
object_ptr<Ui::BoxContent> EditLinkedChatBox(
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
not_null<Window::SessionNavigation*> navigation,
not_null<ChannelData*> channel,
ChannelData *chat,
@@ -298,27 +236,77 @@ object_ptr<Ui::BoxContent> EditLinkedChatBox(
Fn<void(ChannelData*)> callback) {
Expects((channel->isBroadcast() && canEdit) || (chat != nullptr));
const auto init = [=](not_null<PeerListBox*> box) {
class ListBox final : public PeerListBox {
public:
ListBox(
QWidget *parent,
std::unique_ptr<PeerListController> controller,
Fn<void(not_null<ListBox*>)> init)
: PeerListBox(
parent,
std::move(controller),
[=](not_null<PeerListBox*>) { init(this); }) {
}
void showFinished() override {
_showFinished.fire({});
}
rpl::producer<> showFinishes() const {
return _showFinished.events();
}
private:
rpl::event_stream<> _showFinished;
};
const auto init = [=](not_null<ListBox*> box) {
auto above = object_ptr<Ui::VerticalLayout>(box);
above->add(
SetupAbout(above, channel, chat),
st::linkedChatAboutPadding);
Settings::AddDividerTextWithLottie(
above,
box->showFinishes(),
About(channel, chat),
u"discussion"_q);
if (!chat) {
above->add(SetupCreateGroup(
Assert(channel->isBroadcast());
Settings::AddSkip(above);
Settings::AddButton(
above,
navigation,
channel,
callback));
tr::lng_manage_discussion_group_create(),
st::infoCreateLinkedChatButton,
{ &st::settingsIconChat, Settings::kIconLightBlue }
)->addClickHandler([=, parent = above.data()] {
const auto guarded = crl::guard(parent, callback);
Window::Show(navigation).showBox(
Box<GroupInfoBox>(
navigation,
GroupInfoBox::Type::Megagroup,
channel->name() + " Chat",
guarded),
Ui::LayerOption::KeepOther);
});
}
box->peerListSetAboveWidget(std::move(above));
auto below = object_ptr<Ui::VerticalLayout>(box);
if (chat && canEdit) {
below->add(SetupUnlink(below, channel, callback));
Settings::AddButton(
below,
(channel->isBroadcast()
? tr::lng_manage_discussion_group_unlink
: tr::lng_manage_linked_channel_unlink)(),
st::infoUnlinkChatButton,
{ &st::settingsIconMinus, Settings::kIconRed }
)->addClickHandler([=] { callback(nullptr); });
Settings::AddSkip(below);
}
below->add(
SetupFooter(below, channel),
st::linkedChatAboutPadding);
Settings::AddDividerText(
below,
(channel->isBroadcast()
? tr::lng_manage_discussion_group_posted
: tr::lng_manage_linked_channel_posted)());
box->peerListSetBelowWidget(std::move(below));
box->setTitle(channel->isBroadcast()
@@ -339,7 +327,7 @@ object_ptr<Ui::BoxContent> EditLinkedChatBox(
std::move(chats),
std::move(callback),
std::move(showHistoryCallback));
return Box<PeerListBox>(std::move(controller), init);
return Box<ListBox>(std::move(controller), init);
}
} // namespace

View File

@@ -17,14 +17,14 @@ namespace Window {
class SessionNavigation;
} // namespace Window
object_ptr<Ui::BoxContent> EditLinkedChatBox(
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
not_null<Window::SessionNavigation*> navigation,
not_null<ChannelData*> channel,
not_null<ChannelData*> chat,
bool canEdit,
Fn<void(ChannelData*)> callback);
object_ptr<Ui::BoxContent> EditLinkedChatBox(
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
not_null<Window::SessionNavigation*> navigation,
not_null<ChannelData*> channel,
std::vector<not_null<PeerData*>> &&chats,

View File

@@ -1913,7 +1913,7 @@ void Controller::deleteChannel() {
const auto session = &_peer->session();
_navigation->parentController()->hideLayer();
Core::App().closeChatFromWindows(_peer);
Core::App().closeChatFromWindows(channel);
if (chat) {
session->api().deleteConversation(chat, false);
}

View File

@@ -35,8 +35,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kFullArcLength = 360 * 16;
enum class Color {
Permanent,
Expiring,
@@ -740,7 +738,7 @@ void LinksController::rowPaintIcon(
margins,
margins,
margins,
}), (kFullArcLength / 4), kFullArcLength * (1. - progress));
}), arc::kQuarterLength, arc::kFullLength * (1. - progress));
}
}

View File

@@ -348,8 +348,9 @@ void SendFilesBox::enqueueNextPrepare() {
_list.filesToProcess.pop_front();
const auto weak = Ui::MakeWeak(this);
_preparing = true;
crl::async([weak, file = std::move(file)]() mutable {
Storage::PrepareDetails(file, st::sendMediaPreviewSize);
const auto sideLimit = PhotoSideLimit(); // Get on main thread.
crl::async([weak, sideLimit, file = std::move(file)]() mutable {
Storage::PrepareDetails(file, st::sendMediaPreviewSize, sideLimit);
crl::on_main([weak, file = std::move(file)]() mutable {
if (weak) {
weak->addPreparedAsyncFile(std::move(file));
@@ -975,14 +976,11 @@ bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
QString());
if (result.error == Ui::PreparedList::Error::None) {
return result;
} else if (data->hasImage()) {
auto image = qvariant_cast<QImage>(data->imageData());
if (!image.isNull()) {
return Storage::PrepareMediaFromImage(
std::move(image),
QByteArray(),
st::sendMediaPreviewSize);
}
} else if (auto read = Core::ReadMimeImage(data)) {
return Storage::PrepareMediaFromImage(
std::move(read.image),
std::move(read.content),
st::sendMediaPreviewSize);
}
return result;
}();

View File

@@ -369,6 +369,10 @@ callCameraReDial: IconButton(callReDial) {
icon: icon {{ "calls/call_camera_active", menuIconFg }};
iconOver: icon {{ "calls/call_camera_active", menuIconFgOver }};
}
callGroupCall: IconButton(callCameraReDial) {
icon: icon {{ "top_bar_group_call", menuIconFg }};
iconOver: icon {{ "top_bar_group_call", menuIconFgOver }};
}
callRatingPadding: margins(24px, 12px, 24px, 0px);
callRatingStar: IconButton {

View File

@@ -25,6 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "data/data_media_types.h"
#include "data/data_user.h"
#include "data/data_peer_values.h" // Data::ChannelHasActiveCall.
#include "data/data_group_call.h"
#include "data/data_channel.h"
#include "boxes/delete_messages_box.h"
#include "base/unixtime.h"
#include "api/api_updates.h"
@@ -40,8 +43,200 @@ namespace {
constexpr auto kFirstPageCount = 20;
constexpr auto kPerPageCount = 100;
class GroupCallRow final : public PeerListRow {
public:
GroupCallRow(not_null<PeerData*> peer);
void rightActionAddRipple(
QPoint point,
Fn<void()> updateCallback) override;
void rightActionStopLastRipple() override;
int paintNameIconGetWidth(
Painter &p,
Fn<void()> repaint,
crl::time now,
int nameLeft,
int nameTop,
int nameWidth,
int availableWidth,
int outerWidth,
bool selected) override {
return 0;
}
QSize rightActionSize() const override {
return peer()->isChannel() ? QSize(_st.width, _st.height) : QSize();
}
QMargins rightActionMargins() const override {
return QMargins(
0,
0,
st::defaultPeerListItem.photoPosition.x(),
0);
}
void rightActionPaint(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) override;
private:
const style::IconButton &_st;
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
};
GroupCallRow::GroupCallRow(not_null<PeerData*> peer)
: PeerListRow(peer)
, _st(st::callGroupCall) {
if (const auto channel = peer->asChannel()) {
const auto status = (channel->isMegagroup()
? (channel->isPublic()
? tr::lng_create_public_channel_title
: tr::lng_create_private_channel_title)
: (channel->isPublic()
? tr::lng_create_public_group_title
: tr::lng_create_private_group_title))(tr::now);
setCustomStatus(status.toLower());
}
}
void GroupCallRow::rightActionPaint(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) {
auto size = rightActionSize();
if (_actionRipple) {
_actionRipple->paint(
p,
x + _st.rippleAreaPosition.x(),
y + _st.rippleAreaPosition.y(),
outerWidth);
if (_actionRipple->empty()) {
_actionRipple.reset();
}
}
_st.icon.paintInCenter(
p,
style::rtlrect(x, y, size.width(), size.height(), outerWidth));
}
void GroupCallRow::rightActionAddRipple(
QPoint point,
Fn<void()> updateCallback) {
if (!_actionRipple) {
auto mask = Ui::RippleAnimation::EllipseMask(
QSize(_st.rippleAreaSize, _st.rippleAreaSize));
_actionRipple = std::make_unique<Ui::RippleAnimation>(
_st.ripple,
std::move(mask),
std::move(updateCallback));
}
_actionRipple->add(point - _st.rippleAreaPosition);
}
void GroupCallRow::rightActionStopLastRipple() {
if (_actionRipple) {
_actionRipple->lastStop();
}
}
} // namespace
namespace GroupCalls {
ListController::ListController(not_null<Window::SessionController*> window)
: _window(window) {
setStyleOverrides(&st::peerListSingleRow);
}
Main::Session &ListController::session() const {
return _window->session();
}
void ListController::prepare() {
const auto removeRow = [=](not_null<PeerData*> peer) {
const auto it = _groupCalls.find(peer->id);
if (it != end(_groupCalls)) {
const auto &row = it->second;
delegate()->peerListRemoveRow(row);
_groupCalls.erase(it);
}
};
const auto createRow = [=](not_null<PeerData*> peer) {
const auto it = _groupCalls.find(peer->id);
if (it == end(_groupCalls)) {
auto row = std::make_unique<GroupCallRow>(peer);
_groupCalls.emplace(peer->id, row.get());
delegate()->peerListAppendRow(std::move(row));
}
};
const auto processPeer = [=](PeerData *peer) {
if (!peer) {
return;
}
const auto channel = peer->asChannel();
if (channel && Data::ChannelHasActiveCall(channel)) {
createRow(peer);
} else {
removeRow(peer);
}
};
const auto finishProcess = [=] {
delegate()->peerListRefreshRows();
_fullCount = delegate()->peerListFullRowsCount();
};
session().changes().peerUpdates(
Data::PeerUpdate::Flag::GroupCall
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
processPeer(update.peer);
finishProcess();
}, lifetime());
{
auto count = 0;
const auto list = session().data().chatsList(nullptr);
for (const auto &key : list->pinned()->order()) {
processPeer(key.peer());
}
for (const auto &key : list->indexed()->all()) {
if (count > kFirstPageCount) {
break;
}
processPeer(key->key().peer());
count++;
}
finishProcess();
}
}
rpl::producer<bool> ListController::shownValue() const {
return _fullCount.value(
) | rpl::map(rpl::mappers::_1 > 0) | rpl::distinct_until_changed();
}
void ListController::rowClicked(not_null<PeerListRow*> row) {
const auto window = _window;
crl::on_main(window, [=, peer = row->peer()] {
window->showPeerHistory(
peer,
Window::SectionShow::Way::ClearStack);
});
}
void ListController::rowRightActionClicked(not_null<PeerListRow*> row) {
_window->startOrJoinGroupCall(row->peer());
}
} // namespace GroupCalls
class BoxController::Row : public PeerListRow {
public:
Row(not_null<HistoryItem*> item);

View File

@@ -15,6 +15,27 @@ class SessionController;
} // namespace Window
namespace Calls {
namespace GroupCalls {
class ListController : public PeerListController {
public:
explicit ListController(not_null<Window::SessionController*> window);
[[nodiscard]] rpl::producer<bool> shownValue() const;
Main::Session &session() const override;
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
void rowRightActionClicked(not_null<PeerListRow*> row) override;
private:
const not_null<Window::SessionController*> _window;
base::flat_map<PeerId, not_null<PeerListRow*>> _groupCalls;
rpl::variable<int> _fullCount;
};
} // namespace GroupCalls
class BoxController : public PeerListController {
public:
@@ -34,6 +55,7 @@ private:
void receivedCalls(const QVector<MTPMessage> &result);
void refreshAbout();
class GroupCallRow;
class Row;
Row *rowForItem(not_null<const HistoryItem*> item);

View File

@@ -275,6 +275,14 @@ TopBar::TopBar(
, _updateDurationTimer([=] { updateDurationText(); }) {
initControls();
resize(width(), st::callBarHeight);
setupInitialBrush();
}
void TopBar::setupInitialBrush() {
Expects(_switchStateCallback != nullptr);
_switchStateAnimation.stop();
_switchStateCallback(1.);
}
void TopBar::initControls() {
@@ -316,14 +324,16 @@ void TopBar::initControls() {
| MapPushToTalkToActive()
| rpl::distinct_until_changed()
| rpl::type_erased()),
_groupCall->instanceStateValue(),
rpl::single(
_groupCall->instanceState()
) | rpl::then(_groupCall->instanceStateValue() | rpl::filter(
_1 != GroupCall::InstanceState::TransitionToRtc)),
rpl::single(
_groupCall->scheduleDate()
) | rpl::then(_groupCall->real(
) | rpl::map([](not_null<Data::GroupCall*> call) {
return call->scheduleDateValue();
}) | rpl::flatten_latest())
) | rpl::filter(_2 != GroupCall::InstanceState::TransitionToRtc);
}) | rpl::flatten_latest()));
std::move(
muted
) | rpl::map(
@@ -350,7 +360,7 @@ void TopBar::initControls() {
const auto crossFrom = (fromMuted != BarState::Active) ? 1. : 0.;
const auto crossTo = (toMuted != BarState::Active) ? 1. : 0.;
auto animationCallback = [=](float64 value) {
_switchStateCallback = [=](float64 value) {
if (_groupCall) {
_groupBrush = QBrush(
_gradients.gradient(fromMuted, toMuted, value));
@@ -366,7 +376,7 @@ void TopBar::initControls() {
_switchStateAnimation.stop();
const auto duration = (to - from) * kSwitchStateDuration;
_switchStateAnimation.start(
std::move(animationCallback),
_switchStateCallback,
from,
to,
duration);
@@ -748,6 +758,9 @@ void TopBar::updateControlsGeometry() {
_gradients.set_points(
QPointF(0, st::callBarHeight / 2),
QPointF(width(), st::callBarHeight / 2));
if (!_switchStateAnimation.animating()) {
_switchStateCallback(1.);
}
}
void TopBar::paintEvent(QPaintEvent *e) {

View File

@@ -68,6 +68,7 @@ private:
const base::weak_ptr<GroupCall> &groupCall);
void initControls();
void setupInitialBrush();
void updateInfoLabels();
void setInfoLabels();
void updateDurationText();
@@ -101,6 +102,7 @@ private:
QBrush _groupBrush;
anim::linear_gradients<BarState> _gradients;
Ui::Animations::Simple _switchStateAnimation;
Fn<void(float64)> _switchStateCallback;
base::Timer _updateDurationTimer;

View File

@@ -1194,10 +1194,7 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
const auto admin = IsGroupCallAdmin(_peer, participantPeer);
const auto session = &_peer->session();
const auto getCurrentWindow = [=]() -> Window::SessionController* {
if (const auto window = Core::App().separateWindowForPeer(
participantPeer)) {
return window->sessionController();
} else if (const auto window = Core::App().primaryWindow()) {
if (const auto window = Core::App().windowFor(participantPeer)) {
if (const auto controller = window->sessionController()) {
if (&controller->session() == session) {
return controller;

View File

@@ -1064,7 +1064,6 @@ void EmojiListWidget::drawCustom(
QPoint position,
int set,
int index) {
position += _innerPosition + _customPosition;
auto &custom = _custom[set];
custom.painted = true;
auto &entry = custom.list[index];

View File

@@ -262,7 +262,7 @@ void Row::paintRadio(QPainter &p) {
}
const auto loading = _loading
? _loading->computeState()
: Ui::RadialState{ 0., 0, FullArcLength };
: Ui::RadialState{ 0., 0, arc::kFullLength };
const auto isToggledSet = v::is<Active>(_state.current());
const auto isActiveSet = isToggledSet || v::is<Loading>(_state.current());
const auto toggled = _toggled.value(isToggledSet ? 1. : 0.);
@@ -301,7 +301,7 @@ void Row::paintRadio(QPainter &p) {
_st->thickness,
pen.color(),
_st->bg);
} else if (loading.arcLength < FullArcLength) {
} else if (loading.arcLength < arc::kFullLength) {
p.drawArc(rect, loading.arcFrom, loading.arcLength);
} else {
p.drawEllipse(rect);

View File

@@ -68,7 +68,7 @@ QImage EmojiImageLoader::prepare(EmojiPtr emoji) const {
{ -1, 1 },
{ 1, 1 },
} };
const auto corrected = int(base::SafeRound(delta / sqrt(2.)));
const auto corrected = int(base::SafeRound(delta / M_SQRT2));
for (const auto &shift : diagonal) {
for (auto i = 0; i != corrected; ++i) {
p.drawImage(QPoint(delta, delta) + shift * (i + 1), tinted);

View File

@@ -65,6 +65,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/player/media_player_instance.h"
#include "media/player/media_player_float.h"
#include "media/clip/media_clip_reader.h" // For Media::Clip::Finish().
#include "media/system_media_controls_manager.h"
#include "window/notifications_manager.h"
#include "window/themes/window_theme.h"
#include "window/window_lock_widgets.h"
@@ -90,6 +91,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/connection_box.h"
#include "boxes/premium_limits_box.h"
#include "ui/boxes/confirm_box.h"
#include "styles/style_window.h"
#include <QtCore/QStandardPaths>
#include <QtCore/QMimeDatabase>
@@ -183,11 +185,11 @@ Application::~Application() {
Local::writeSettings();
}
// Depend on primaryWindow() for now :(
Shortcuts::Finish();
setLastActiveWindow(nullptr);
_windowInSettings = _lastActivePrimaryWindow = nullptr;
_closingAsyncWindows.clear();
_secondaryWindows.clear();
_primaryWindow = nullptr;
_primaryWindows.clear();
_mediaView = nullptr;
_notifications->clearAllFast();
@@ -214,6 +216,8 @@ Application::~Application() {
Window::Theme::Uninitialize();
_mediaControlsManager = nullptr;
Media::Player::finish(_audio.get());
style::stopManager();
@@ -237,8 +241,7 @@ void Application::run() {
refreshGlobalProxy(); // Depends on app settings being read.
if (const auto old = Local::oldSettingsVersion(); old < AppVersion) {
Platform::InstallLauncher();
RegisterUrlScheme();
InvokeQueued(this, [] { RegisterUrlScheme(); });
Platform::NewVersionLaunched(old);
}
@@ -252,15 +255,6 @@ void Application::run() {
return;
}
if (KSandbox::isInside()) {
const auto path = settings().downloadPath();
if (!path.isEmpty()
&& path != FileDialog::Tmp()
&& !base::CanReadDirectory(path)) {
settings().setDownloadPath(QString());
}
}
_translator = std::make_unique<Lang::Translator>();
QCoreApplication::instance()->installTranslator(_translator.get());
@@ -269,10 +263,15 @@ void Application::run() {
Ui::StartCachedCorners();
Ui::Emoji::Init();
Ui::PreloadTextSpoilerMask();
startShortcuts();
startEmojiImageLoader();
startSystemDarkModeViewer();
Media::Player::start(_audio.get());
if (MediaControlsManager::Supported()) {
_mediaControlsManager = std::make_unique<MediaControlsManager>();
}
style::ShortAnimationPlaying(
) | rpl::start_with_next([=](bool playing) {
if (playing) {
@@ -289,13 +288,14 @@ void Application::run() {
// Create mime database, so it won't be slow later.
QMimeDatabase().mimeTypeForName(u"text/plain"_q);
_primaryWindow = std::make_unique<Window::Controller>();
_lastActiveWindow = _primaryWindow.get();
_primaryWindows.emplace(nullptr, std::make_unique<Window::Controller>());
setLastActiveWindow(_primaryWindows.front().second.get());
_windowInSettings = _lastActivePrimaryWindow = _lastActiveWindow;
_domain->activeChanges(
) | rpl::start_with_next([=](not_null<Main::Account*> account) {
_primaryWindow->showAccount(account);
}, _primaryWindow->widget()->lifetime());
showAccount(account);
}, _lifetime);
(
_domain->activeValue(
@@ -312,15 +312,15 @@ void Application::run() {
) | rpl::start_with_next([=](not_null<Main::Account*> account) {
const auto ordered = _domain->orderedAccounts();
const auto it = ranges::find(ordered, account);
if (it != end(ordered)) {
if (_lastActivePrimaryWindow && it != end(ordered)) {
const auto index = std::distance(begin(ordered), it);
if ((index + 1) > _domain->maxAccounts()) {
_primaryWindow->show(Box(
_lastActivePrimaryWindow->show(Box(
AccountsLimitBox,
&account->session()));
}
}
}, _primaryWindow->widget()->lifetime());
}, _lifetime);
QCoreApplication::instance()->installEventFilter(this);
@@ -335,26 +335,23 @@ void Application::run() {
DEBUG_LOG(("Application Info: window created..."));
// Depend on primaryWindow() for now :(
startShortcuts();
startDomain();
startTray();
_primaryWindow->widget()->show();
_lastActivePrimaryWindow->widget()->show();
const auto currentGeometry = _primaryWindow->widget()->geometry();
const auto current = _lastActivePrimaryWindow->widget()->geometry();
_mediaView = std::make_unique<Media::View::OverlayWidget>();
_primaryWindow->widget()->Ui::RpWidget::setGeometry(currentGeometry);
_lastActivePrimaryWindow->widget()->Ui::RpWidget::setGeometry(current);
DEBUG_LOG(("Application Info: showing."));
_primaryWindow->finishFirstShow();
_lastActivePrimaryWindow->finishFirstShow();
if (!_primaryWindow->locked() && cStartToSettings()) {
_primaryWindow->showSettings();
if (!_lastActivePrimaryWindow->locked() && cStartToSettings()) {
_lastActivePrimaryWindow->showSettings();
}
_primaryWindow->updateIsActiveFocus();
_lastActivePrimaryWindow->updateIsActiveFocus();
for (const auto &error : Shortcuts::Errors()) {
LOG(("Shortcuts Error: %1").arg(error));
@@ -371,11 +368,6 @@ void Application::run() {
_mediaView->show(std::move(request));
}
}, _lifetime);
_primaryWindow->openInMediaViewRequests(
) | rpl::start_to_stream(
_openInMediaViewRequests,
_primaryWindow->lifetime());
{
const auto countries = std::make_shared<Countries::Manager>(
_domain.get());
@@ -383,6 +375,25 @@ void Application::run() {
[[maybe_unused]] const auto countriesCopy = countries;
});
}
processCreatedWindow(_lastActivePrimaryWindow);
}
void Application::showAccount(not_null<Main::Account*> account) {
if (const auto separate = separateWindowForAccount(account)) {
_lastActivePrimaryWindow = separate;
separate->activate();
} else if (const auto last = activePrimaryWindow()) {
for (auto &[key, window] : _primaryWindows) {
if (window.get() == last && key != account.get()) {
auto found = std::move(window);
_primaryWindows.remove(key);
_primaryWindows.emplace(account, std::move(found));
break;
}
}
last->showAccount(account);
}
}
void Application::showOpenGLCrashNotification() {
@@ -399,7 +410,7 @@ void Application::showOpenGLCrashNotification() {
Core::App().settings().setDisableOpenGL(true);
Local::writeSettings();
};
_primaryWindow->show(Ui::MakeConfirmBox({
_lastActivePrimaryWindow->show(Ui::MakeConfirmBox({
.text = ""
"There may be a problem with your graphics drivers and OpenGL. "
"Try updating your drivers.\n\n"
@@ -456,15 +467,15 @@ void Application::startSystemDarkModeViewer() {
void Application::enumerateWindows(Fn<void(
not_null<Window::Controller*>)> callback) const {
if (_primaryWindow) {
callback(_primaryWindow.get());
for (const auto &window : ranges::views::values(_primaryWindows)) {
callback(window.get());
}
for (const auto &window : ranges::views::values(_secondaryWindows)) {
callback(window.get());
}
}
void Application::processSecondaryWindow(
void Application::processCreatedWindow(
not_null<Window::Controller*> window) {
window->openInMediaViewRequests(
) | rpl::start_to_stream(_openInMediaViewRequests, window->lifetime());
@@ -477,21 +488,35 @@ void Application::startTray() {
) | rpl::start_with_next([=] {
enumerateWindows([&](WindowRaw w) { w->updateIsActive(); });
_tray->updateMenuText();
}, _primaryWindow->widget()->lifetime());
}, _lifetime);
_tray->showFromTrayRequests(
) | rpl::start_with_next([=] {
const auto last = _lastActiveWindow;
enumerateWindows([&](WindowRaw w) { w->widget()->showFromTray(); });
if (last) {
last->widget()->showFromTray();
}
}, _primaryWindow->widget()->lifetime());
activate();
}, _lifetime);
_tray->hideToTrayRequests(
) | rpl::start_with_next([=] {
enumerateWindows([&](WindowRaw w) { w->widget()->minimizeToTray(); });
}, _primaryWindow->widget()->lifetime());
enumerateWindows([&](WindowRaw w) {
w->widget()->minimizeToTray();
});
}, _lifetime);
}
void Application::activate() {
const auto last = _lastActiveWindow;
const auto primary = _lastActivePrimaryWindow;
enumerateWindows([&](not_null<Window::Controller*> w) {
if (w != last && w != primary) {
w->widget()->showFromTray();
}
});
if (primary) {
primary->widget()->showFromTray();
}
if (last && last != primary) {
last->widget()->showFromTray();
}
}
auto Application::prepareEmojiSourceImages()
@@ -513,10 +538,10 @@ void Application::clearEmojiSourceImages() {
}
bool Application::isActiveForTrayMenu() const {
if (_primaryWindow && _primaryWindow->widget()->isActiveForTrayMenu()) {
return true;
}
return ranges::any_of(ranges::views::values(_secondaryWindows), [=](
return ranges::any_of(ranges::views::values(_primaryWindows), [=](
const std::unique_ptr<Window::Controller> &controller) {
return controller->widget()->isActiveForTrayMenu();
}) || ranges::any_of(ranges::views::values(_secondaryWindows), [=](
const std::unique_ptr<Window::Controller> &controller) {
return controller->widget()->isActiveForTrayMenu();
});
@@ -551,7 +576,7 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
const auto event = static_cast<QShortcutEvent*>(e);
DEBUG_LOG(("Shortcut event caught: %1"
).arg(event->key().toString()));
if (Shortcuts::HandleEvent(event)) {
if (Shortcuts::HandleEvent(object, event)) {
return true;
}
} break;
@@ -571,8 +596,8 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
cSetStartUrl(url.mid(0, 8192));
checkStartUrl();
}
if (StartUrlRequiresActivate(url)) {
_primaryWindow->activate();
if (_lastActivePrimaryWindow && StartUrlRequiresActivate(url)) {
_lastActivePrimaryWindow->activate();
}
}
} break;
@@ -606,8 +631,7 @@ bool Application::canReadDefaultDownloadPath(bool always) const {
}
bool Application::canSaveFileWithoutAskingForPath() const {
return !Core::App().settings().askDownloadPath()
&& canReadDefaultDownloadPath();
return !Core::App().settings().askDownloadPath();
}
MTP::Config &Application::fallbackProductionConfig() const {
@@ -714,35 +738,12 @@ bool Application::screenIsLocked() const {
return _screenIsLocked;
}
void Application::setDefaultFloatPlayerDelegate(
not_null<Media::Player::FloatDelegate*> delegate) {
Expects(!_defaultFloatPlayerDelegate == !_floatPlayers);
_defaultFloatPlayerDelegate = delegate;
_replacementFloatPlayerDelegate = nullptr;
if (_floatPlayers) {
_floatPlayers->replaceDelegate(delegate);
} else {
_floatPlayers = std::make_unique<Media::Player::FloatController>(
delegate);
}
}
void Application::replaceFloatPlayerDelegate(
not_null<Media::Player::FloatDelegate*> replacement) {
Expects(_floatPlayers != nullptr);
_replacementFloatPlayerDelegate = replacement;
_floatPlayers->replaceDelegate(replacement);
}
void Application::restoreFloatPlayerDelegate(
not_null<Media::Player::FloatDelegate*> replacement) {
Expects(_floatPlayers != nullptr);
if (_replacementFloatPlayerDelegate == replacement) {
_replacementFloatPlayerDelegate = nullptr;
_floatPlayers->replaceDelegate(_defaultFloatPlayerDelegate);
void Application::floatPlayerToggleGifsPaused(bool paused) {
_floatPlayerGifsPaused = paused;
if (_lastActiveWindow) {
if (const auto delegate = _lastActiveWindow->floatPlayerDelegate()) {
delegate->floatPlayerToggleGifsPaused(paused);
}
}
}
@@ -814,15 +815,15 @@ void Application::checkLocalTime() {
void Application::handleAppActivated() {
checkLocalTime();
if (_primaryWindow) {
_primaryWindow->updateIsActiveFocus();
if (_lastActiveWindow) {
_lastActiveWindow->updateIsActiveFocus();
}
}
void Application::handleAppDeactivated() {
if (_primaryWindow) {
_primaryWindow->updateIsActiveBlur();
}
enumerateWindows([&](not_null<Window::Controller*> w) {
w->updateIsActiveBlur();
});
const auto session = _lastActiveWindow
? _lastActiveWindow->maybeSession()
: nullptr;
@@ -855,8 +856,8 @@ void Application::switchDebugMode() {
Logs::SetDebugEnabled(true);
_launcher->writeDebugModeSetting();
DEBUG_LOG(("Debug logs started."));
if (_primaryWindow) {
_primaryWindow->hideLayer();
if (_lastActivePrimaryWindow) {
_lastActivePrimaryWindow->hideLayer();
}
}
}
@@ -969,13 +970,17 @@ bool Application::canApplyLangPackWithoutRestart() const {
}
void Application::checkSendPaths() {
if (!cSendPaths().isEmpty() && _primaryWindow && !_primaryWindow->locked()) {
_primaryWindow->widget()->sendPaths();
if (!cSendPaths().isEmpty()
&& _lastActivePrimaryWindow
&& !_lastActivePrimaryWindow->locked()) {
_lastActivePrimaryWindow->widget()->sendPaths();
}
}
void Application::checkStartUrl() {
if (!cStartUrl().isEmpty() && _primaryWindow && !_primaryWindow->locked()) {
if (!cStartUrl().isEmpty()
&& _lastActivePrimaryWindow
&& !_lastActivePrimaryWindow->locked()) {
const auto url = cStartUrl();
cSetStartUrl(QString());
if (!openLocalUrl(url, {})) {
@@ -1039,8 +1044,8 @@ bool Application::openCustomUrl(
const auto my = context.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get()
? my.sessionWindow.get()
: _primaryWindow
? _primaryWindow->sessionController()
: _lastActivePrimaryWindow
? _lastActivePrimaryWindow->sessionController()
: nullptr;
using namespace qthelp;
@@ -1052,11 +1057,10 @@ bool Application::openCustomUrl(
}
}
return false;
}
void Application::preventOrInvoke(Fn<void()> &&callback) {
_primaryWindow->preventOrInvoke(std::move(callback));
_lastActivePrimaryWindow->preventOrInvoke(std::move(callback));
}
void Application::lockByPasscode() {
@@ -1159,8 +1163,13 @@ void Application::localPasscodeChanged() {
checkAutoLock(crl::now());
}
bool Application::savingPositionFor(
not_null<Window::Controller*> window) const {
return !_windowInSettings || (_windowInSettings == window);
}
bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
if (Quitting() || !_primaryWindow) {
if (Quitting() || !_lastActiveWindow) {
return false;
} else if (_calls->hasActivePanel(session)) {
return true;
@@ -1171,8 +1180,18 @@ bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
return false;
}
Window::Controller *Application::primaryWindow() const {
return _primaryWindow.get();
Window::Controller *Application::activePrimaryWindow() const {
return _lastActivePrimaryWindow;
}
Window::Controller *Application::separateWindowForAccount(
not_null<Main::Account*> account) const {
for (const auto &[openedAccount, window] : _primaryWindows) {
if (openedAccount == account.get()) {
return window.get();
}
}
return nullptr;
}
Window::Controller *Application::separateWindowForPeer(
@@ -1204,35 +1223,152 @@ Window::Controller *Application::ensureSeparateWindowForPeer(
peer->owner().history(peer),
std::make_unique<Window::Controller>(peer, showAtMsgId)
).first->second.get();
processSecondaryWindow(result);
processCreatedWindow(result);
result->widget()->show();
result->finishFirstShow();
return activate(result);
}
Window::Controller *Application::ensureSeparateWindowForAccount(
not_null<Main::Account*> account) {
const auto activate = [&](not_null<Window::Controller*> window) {
window->activate();
return window;
};
if (const auto existing = separateWindowForAccount(account)) {
return activate(existing);
}
const auto result = _primaryWindows.emplace(
account,
std::make_unique<Window::Controller>(account)
).first->second.get();
processCreatedWindow(result);
result->widget()->show();
result->finishFirstShow();
return activate(result);
}
Window::Controller *Application::windowFor(not_null<PeerData*> peer) const {
if (const auto separate = separateWindowForPeer(peer)) {
return separate;
}
return windowFor(&peer->account());
}
Window::Controller *Application::windowFor(
not_null<Main::Account*> account) const {
if (const auto separate = separateWindowForAccount(account)) {
return separate;
}
return activePrimaryWindow();
}
Window::Controller *Application::activeWindow() const {
return _lastActiveWindow;
}
bool Application::closeNonLastAsync(not_null<Window::Controller*> window) {
const auto hasOther = [&] {
for (const auto &[account, primary] : _primaryWindows) {
if (!_closingAsyncWindows.contains(primary.get())
&& primary.get() != window
&& primary->maybeSession()) {
return true;
}
}
return false;
}();
if (!hasOther) {
return false;
}
_closingAsyncWindows.emplace(window);
crl::on_main(window, [=] { closeWindow(window); });
return true;
}
void Application::setLastActiveWindow(Window::Controller *window) {
_floatPlayerDelegateLifetime.destroy();
if (_floatPlayerGifsPaused && _lastActiveWindow) {
if (const auto delegate = _lastActiveWindow->floatPlayerDelegate()) {
delegate->floatPlayerToggleGifsPaused(false);
}
}
_lastActiveWindow = window;
if (!window) {
_floatPlayers = nullptr;
return;
}
window->floatPlayerDelegateValue(
) | rpl::start_with_next([=](Media::Player::FloatDelegate *value) {
if (!value) {
_floatPlayers = nullptr;
} else if (_floatPlayers) {
_floatPlayers->replaceDelegate(value);
} else if (value) {
_floatPlayers = std::make_unique<Media::Player::FloatController>(
value);
}
if (value && _floatPlayerGifsPaused) {
value->floatPlayerToggleGifsPaused(true);
}
}, _floatPlayerDelegateLifetime);
}
void Application::closeWindow(not_null<Window::Controller*> window) {
const auto next = (_primaryWindows.front().second.get() != window)
? _primaryWindows.front().second.get()
: (_primaryWindows.back().second.get() != window)
? _primaryWindows.back().second.get()
: nullptr;
if (_lastActivePrimaryWindow == window) {
_lastActivePrimaryWindow = next;
}
if (_windowInSettings == window) {
_windowInSettings = next;
}
if (_lastActiveWindow == window) {
setLastActiveWindow(next);
if (_lastActiveWindow) {
_lastActiveWindow->activate();
_lastActiveWindow->widget()->updateGlobalMenu();
}
}
_closingAsyncWindows.remove(window);
for (auto i = begin(_primaryWindows); i != end(_primaryWindows);) {
if (i->second.get() == window) {
Assert(_lastActiveWindow != window);
Assert(_lastActivePrimaryWindow != window);
Assert(_windowInSettings != window);
i = _primaryWindows.erase(i);
} else {
++i;
}
}
for (auto i = begin(_secondaryWindows); i != end(_secondaryWindows);) {
if (i->second.get() == window) {
if (_lastActiveWindow == window) {
_lastActiveWindow = _primaryWindow.get();
}
Assert(_lastActiveWindow != window);
i = _secondaryWindows.erase(i);
} else {
++i;
}
}
const auto account = domain().started()
? &domain().active()
: nullptr;
if (account && !_primaryWindows.contains(account) && _lastActiveWindow) {
domain().activate(&_lastActiveWindow->account());
}
}
void Application::closeChatFromWindows(not_null<PeerData*> peer) {
if (const auto window = windowFor(peer)
; window && !window->isPrimary()) {
closeWindow(window);
}
for (const auto &[history, window] : _secondaryWindows) {
if (history->peer == peer) {
closeWindow(window.get());
break;
} else if (const auto session = window->sessionController()) {
if (const auto session = window->sessionController()) {
if (session->activeChatCurrent().peer() == peer) {
session->showPeerHistory(
window->singlePeer()->id,
@@ -1240,8 +1376,8 @@ void Application::closeChatFromWindows(not_null<PeerData*> peer) {
}
}
}
if (_primaryWindow && _primaryWindow->sessionController()) {
const auto primary = _primaryWindow->sessionController();
if (const auto window = windowFor(&peer->account())) {
const auto primary = window->sessionController();
if ((primary->activeChatCurrent().peer() == peer)
&& (&primary->session() == &peer->session())) {
primary->clearSectionStack();
@@ -1257,7 +1393,13 @@ void Application::closeChatFromWindows(not_null<PeerData*> peer) {
void Application::windowActivated(not_null<Window::Controller*> window) {
const auto was = _lastActiveWindow;
const auto now = window;
_lastActiveWindow = window;
setLastActiveWindow(window);
if (window->isPrimary()) {
_lastActivePrimaryWindow = window;
}
window->widget()->updateGlobalMenu();
const auto wasSession = was ? was->maybeSession() : nullptr;
const auto nowSession = now->maybeSession();
@@ -1420,8 +1562,8 @@ void Application::quitPreventFinished() {
}
void Application::quitDelayed() {
if (_primaryWindow) {
_primaryWindow->widget()->hide();
for (const auto &[account, window] : _primaryWindows) {
window->widget()->hide();
}
for (const auto &[history, window] : _secondaryWindows) {
window->widget()->hide();

View File

@@ -71,6 +71,7 @@ namespace Player {
class FloatController;
class FloatDelegate;
} // namespace Player
class SystemMediaControlsManager;
} // namespace Media
namespace Lang {
@@ -152,13 +153,25 @@ public:
// Windows interface.
bool hasActiveWindow(not_null<Main::Session*> session) const;
[[nodiscard]] Window::Controller *primaryWindow() const;
[[nodiscard]] bool savingPositionFor(
not_null<Window::Controller*> window) const;
[[nodiscard]] Window::Controller *activeWindow() const;
[[nodiscard]] Window::Controller *activePrimaryWindow() const;
[[nodiscard]] Window::Controller *separateWindowForAccount(
not_null<Main::Account*> account) const;
[[nodiscard]] Window::Controller *separateWindowForPeer(
not_null<PeerData*> peer) const;
Window::Controller *ensureSeparateWindowForPeer(
not_null<PeerData*> peer,
MsgId showAtMsgId);
Window::Controller *ensureSeparateWindowForAccount(
not_null<Main::Account*> account);
[[nodiscard]] Window::Controller *windowFor( // Doesn't auto-switch.
not_null<PeerData*> peer) const;
[[nodiscard]] Window::Controller *windowFor( // Doesn't auto-switch.
not_null<Main::Account*> account) const;
[[nodiscard]] bool closeNonLastAsync(
not_null<Window::Controller*> window);
void closeWindow(not_null<Window::Controller*> window);
void windowActivated(not_null<Window::Controller*> window);
bool closeActiveWindow();
@@ -168,6 +181,7 @@ public:
void checkSystemDarkMode();
[[nodiscard]] bool isActiveForTrayMenu() const;
void closeChatFromWindows(not_null<PeerData*> peer);
void activate();
// Media view interface.
bool hideMediaView();
@@ -244,12 +258,7 @@ public:
[[nodiscard]] QString changelogLink() const;
// Float player.
void setDefaultFloatPlayerDelegate(
not_null<Media::Player::FloatDelegate*> delegate);
void replaceFloatPlayerDelegate(
not_null<Media::Player::FloatDelegate*> replacement);
void restoreFloatPlayerDelegate(
not_null<Media::Player::FloatDelegate*> replacement);
void floatPlayerToggleGifsPaused(bool paused);
[[nodiscard]] rpl::producer<FullMsgId> floatPlayerClosed() const;
// Calls.
@@ -325,9 +334,11 @@ private:
void startSystemDarkModeViewer();
void startTray();
void setLastActiveWindow(Window::Controller *window);
void showAccount(not_null<Main::Account*> account);
void enumerateWindows(
Fn<void(not_null<Window::Controller*>)> callback) const;
void processSecondaryWindow(not_null<Window::Controller*> window);
void processCreatedWindow(not_null<Window::Controller*> window);
friend void QuitAttempt();
void quitDelayed();
@@ -371,15 +382,22 @@ private:
// Mutable because is created in run() after OpenSSL is inited.
std::unique_ptr<Window::Notifications::System> _notifications;
using MediaControlsManager = Media::SystemMediaControlsManager;
std::unique_ptr<MediaControlsManager> _mediaControlsManager;
const std::unique_ptr<Data::DownloadManager> _downloadManager;
const std::unique_ptr<Main::Domain> _domain;
const std::unique_ptr<Export::Manager> _exportManager;
const std::unique_ptr<Calls::Instance> _calls;
std::unique_ptr<Window::Controller> _primaryWindow;
base::flat_map<
Main::Account*,
std::unique_ptr<Window::Controller>> _primaryWindows;
base::flat_set<not_null<Window::Controller*>> _closingAsyncWindows;
base::flat_map<
not_null<History*>,
std::unique_ptr<Window::Controller>> _secondaryWindows;
Window::Controller *_lastActiveWindow = nullptr;
Window::Controller *_lastActivePrimaryWindow = nullptr;
Window::Controller *_windowInSettings = nullptr;
std::unique_ptr<Media::View::OverlayWidget> _mediaView;
const std::unique_ptr<Lang::Instance> _langpack;
@@ -391,8 +409,8 @@ private:
const std::unique_ptr<Tray> _tray;
std::unique_ptr<Media::Player::FloatController> _floatPlayers;
Media::Player::FloatDelegate *_defaultFloatPlayerDelegate = nullptr;
Media::Player::FloatDelegate *_replacementFloatPlayerDelegate = nullptr;
rpl::lifetime _floatPlayerDelegateLifetime;
bool _floatPlayerGifsPaused = false;
rpl::variable<bool> _passcodeLock;
bool _screenIsLocked = false;

View File

@@ -22,61 +22,6 @@ namespace {
std::map<int, const char*> BetaLogs() {
return {
{
4000003,
"- Animated emoji for messages.\n"
"- Premium: Privacy settings for voice messages.\n"
"- Premium: Gifting Telegram Premium "
"to any user from their profile page.\n"
},
{
4000004,
"- Allow sending animated emoji to Saved Messages "
"even without Telegram Premium.\n"
"- Premium: Suggest animated emoji by regular emoji "
"(can be disabled in Settings).\n"
"- Premium: Show all suggested premium stickers "
"in a special section of the stickers panel.\n"
"- Premium: Allow hiding premium stickers special section "
"of the stickers panel.\n"
"- Fix a memory leak in RTMP livestreams.\n"
"- Fix some bot webview bugs on macOS.\n"
"- Fix forwarding of voice messages.\n"
},
{
4001002,
"- New reaction selector above the right click menu.\n"
"- Premium: Set any custom emoji reactions in private chats.\n"
"- Premium: Set any custom emoji as your profile status.\n"
"- Insert or copy custom emoji from pack preview.\n"
},
{
4002001,
"- Improve scaling / cropping for photos / video files.\n"
"- Improve touch support in channel comments.\n"
"- Nice animation for spoilers.\n"
},
{
4002002,
"- Fix crash in spoiler revealing in media captions.\n"
"- Fix spoiler revealing in media viewer captions.\n"
"- Fix crash in folder editing on Linux.\n"
},
{
4004002,
"- Send photos and video files hidden by a spoiler effect.\n"
@@ -93,6 +38,43 @@ std::map<int, const char*> BetaLogs() {
"- Fix a crash in own profile photo updating.\n"
"- Bug fixes and other minor improvements.\n"
},
{
4005004,
"- Allow wide range of interface scale options.\n"
"- Show opened chat name in the window title.\n"
"- Bug fixes and other minor improvements.\n"
"- Fix updating on macOS older than 10.14.\n"
},
{
4005006,
"- Try enabling non-fractional scale "
"High DPI support on Windows and Linux.\n"
"- Experimental setting for fractional scale "
"High DPI support on Windows and Linux.\n"
"- Fix navigation to bottom problems in groups you didn't join.\n"
"- Fix a crash in chat export settings changes.\n"
"- Fix a crash in sending some of JPEG images.\n"
"- Fix CJK fonts on Windows.\n"
},
{
4005007,
"- Fix glitches after moving window to another screen.\n",
},
{
4005008,
"- Allow opening another account in a new window "
"(see Settings > Advanced > Experimental Settings).\n"
"- A lot of bugfixes for working with more than one window.\n"
}
};
};

View File

@@ -93,7 +93,8 @@ QString HiddenUrlClickHandler::copyToClipboardContextItemText() const {
}
QString HiddenUrlClickHandler::dragText() const {
return HiddenUrlClickHandler::copyToClipboardText();
const auto result = HiddenUrlClickHandler::copyToClipboardText();
return result.startsWith(u"internal:"_q) ? QString() : result;
}
void HiddenUrlClickHandler::Open(QString url, QVariant context) {

View File

@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/zlib_help.h"
#include <QtWidgets/QFileDialog>
#include <QtGui/QFontInfo>
#include <QtGui/QScreen>
#include <QtGui/QDesktopServices>
#include <QtCore/QStandardPaths>
@@ -43,12 +44,37 @@ PreLaunchWindow::PreLaunchWindow(QString title) {
p.setColor(QPalette::Window, QColor(255, 255, 255));
setPalette(p);
_size = QFontMetrics(font()).height();
_size = QFontInfo(font()).pixelSize();
int paddingVertical = (_size / 2);
int paddingHorizontal = _size;
int borderRadius = (_size / 5);
setStyleSheet(u"QPushButton { padding: %1px %2px; background-color: #ffffff; border-radius: %3px; }\nQPushButton#confirm:hover, QPushButton#cancel:hover { background-color: #e3f1fa; color: #2f9fea; }\nQPushButton#confirm { color: #2f9fea; }\nQPushButton#cancel { color: #aeaeae; }\nQLineEdit { border: 1px solid #e0e0e0; padding: 5px; }\nQLineEdit:focus { border: 2px solid #37a1de; padding: 4px; }"_q.arg(paddingVertical).arg(paddingHorizontal).arg(borderRadius));
setStyleSheet(uR"(
QPushButton {
padding: %1px %2px;
background-color: #ffffff;
border-radius: %3px;
}
QPushButton#confirm:hover,
QPushButton#cancel:hover {
background-color: #e3f1fa;
color: #2f9fea;
}
QPushButton#confirm {
color: #2f9fea;
}
QPushButton#cancel {
color: #aeaeae;
}
QLineEdit {
border: 1px solid #e0e0e0;
padding: 5px;
}
QLineEdit:focus {
border: 2px solid #37a1de;
padding: 4px;
}
)"_q.arg(paddingVertical).arg(paddingHorizontal).arg(borderRadius));
if (!PreLaunchWindowInstance) {
PreLaunchWindowInstance = this;
}
@@ -57,7 +83,7 @@ PreLaunchWindow::PreLaunchWindow(QString title) {
void PreLaunchWindow::activate() {
setWindowState(windowState() & ~Qt::WindowMinimized);
setVisible(true);
psActivateProcess();
Platform::ActivateThisProcess();
raise();
activateWindow();
}

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
#include "storage/localstorage.h"
#include "storage/storage_account.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/base_platform_file_utilities.h"
#include "platform/platform_file_utilities.h"
@@ -75,7 +76,7 @@ QString filedialogDefaultName(
QString base;
if (fileTime) {
const auto date = base::unixtime::parse(fileTime);
base = prefix + QLocale().toString(date, "_yyyy-MM-dd_HH-mm-ss");
base = prefix + date.toString("_yyyy-MM-dd_HH-mm-ss");
} else {
struct tm tm;
time_t t = time(NULL);
@@ -137,9 +138,9 @@ void OpenEmailLink(const QString &email) {
});
}
void OpenWith(const QString &filepath, QPoint menuPosition) {
void OpenWith(const QString &filepath) {
InvokeQueued(QCoreApplication::instance(), [=] {
if (!Platform::File::UnsafeShowOpenWithDropdown(filepath, menuPosition)) {
if (!Platform::File::UnsafeShowOpenWithDropdown(filepath)) {
Ui::PreventDelayedActivation();
if (!Platform::File::UnsafeShowOpenWith(filepath)) {
Platform::File::UnsafeLaunch(filepath);
@@ -171,31 +172,14 @@ QString DefaultDownloadPathFolder(not_null<Main::Session*> session) {
}
QString DefaultDownloadPath(not_null<Main::Session*> session) {
const auto realDefaultPath = QStandardPaths::writableLocation(
if (!Core::App().canReadDefaultDownloadPath()) {
return session->local().tempDirectory();
}
return QStandardPaths::writableLocation(
QStandardPaths::DownloadLocation)
+ '/'
+ DefaultDownloadPathFolder(session)
+ '/';
if (!Core::App().canReadDefaultDownloadPath()) {
QStringList files;
QByteArray remoteContent;
const auto success = Platform::FileDialog::Get(
nullptr,
files,
remoteContent,
tr::lng_download_path_choose(tr::now),
QString(),
FileDialog::internal::Type::ReadFolder,
realDefaultPath);
if (success && !files.isEmpty() && !files[0].isEmpty()) {
const auto result = files[0].endsWith('/') ? files[0] : (files[0] + '/');
Core::App().settings().setDownloadPath(result);
Core::App().saveSettings();
return result;
}
return QString();
}
return realDefaultPath;
}
namespace internal {

View File

@@ -34,7 +34,7 @@ namespace File {
// Those functions are async wrappers to Platform::File::Unsafe* calls.
void OpenUrl(const QString &url);
void OpenEmailLink(const QString &email);
void OpenWith(const QString &filepath, QPoint menuPosition);
void OpenWith(const QString &filepath);
void Launch(const QString &filepath);
void ShowInFolder(const QString &filepath);

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/platform_launcher.h"
#include "platform/platform_specific.h"
#include "base/options.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/base_platform_file_utilities.h"
#include "ui/main_queue_processor.h"
@@ -272,8 +273,18 @@ bool CheckPortableVersionFolder() {
return true;
}
base::options::toggle OptionFractionalScalingEnabled({
.id = kOptionFractionalScalingEnabled,
.name = "Enable precise High DPI scaling",
.description = "Follow system interface scale settings exactly.",
.scope = base::options::windows | base::options::linux,
.restartRequired = true,
});
} // namespace
const char kOptionFractionalScalingEnabled[] = "fractional-scaling-enabled";
std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) {
return std::make_unique<Platform::Launcher>(argc, argv);
}
@@ -294,9 +305,6 @@ void Launcher::init() {
initQtMessageLogging();
QApplication::setApplicationName(u"TelegramDesktop"_q);
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
QApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::Floor);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
// fallback session management is useless for tdesktop since it doesn't have
@@ -311,6 +319,17 @@ void Launcher::init() {
initHook();
}
void Launcher::initHighDpi() {
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
if (OptionFractionalScalingEnabled.value()) {
QApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
} else {
QApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor);
}
}
int Launcher::exec() {
init();
@@ -324,6 +343,9 @@ int Launcher::exec() {
Logs::start(this);
base::options::init(cWorkingDir() + "tdata/experimental_options.json");
// Must be called after options are inited.
initHighDpi();
if (Logs::DebugEnabled()) {
const auto openalLogPath = QDir::toNativeSeparators(
cWorkingDir() + u"DebugLogs/last_openal_log.txt"_q);

View File

@@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Core {
extern const char kOptionFractionalScalingEnabled[];
class Launcher {
public:
Launcher(int argc, char *argv[]);
@@ -52,6 +54,7 @@ private:
void init();
virtual void initHook() {
}
virtual void initHighDpi();
virtual bool launchUpdater(UpdaterLaunch action) = 0;

View File

@@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_premium.h"
#include "mainwidget.h"
#include "main/main_account.h"
#include "main/main_domain.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "inline_bots/bot_attach_web_view.h"
@@ -798,12 +799,13 @@ bool ResolveLoginCode(
const Match &match,
const QVariant &context) {
const auto loginCode = match->captured(2);
if (loginCode.isEmpty()) {
const auto &domain = Core::App().domain();
if (loginCode.isEmpty() || (!controller && !domain.started())) {
return false;
};
(controller
? controller->session().account()
: Core::App().activeAccount()).handleLoginCode(loginCode);
: domain.active()).handleLoginCode(loginCode);
if (controller) {
controller->window().activate();
} else if (const auto window = Core::App().activeWindow()) {
@@ -869,7 +871,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
ResolvePrivatePost
},
{
u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information)?$"_q,
u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information|/edit_profile)?$"_q,
ResolveSettings
},
{

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/mime_type.h"
#include "core/utils.h"
#include "ui/image/image_prepare.h"
#include <QtCore/QMimeDatabase>
#include <QtCore/QMimeData>
@@ -164,10 +165,31 @@ std::shared_ptr<QMimeData> ShareMimeMediaData(
if (original->hasImage()) {
result->setImageData(original->imageData());
}
if (original->hasFormat(u"application/x-td-use-jpeg"_q)
&& original->hasFormat(u"image/jpeg"_q)) {
result->setData(u"application/x-td-use-jpeg"_q, "1");
result->setData(u"image/jpeg"_q, original->data(u"image/jpeg"_q));
}
if (auto list = base::GetMimeUrls(original); !list.isEmpty()) {
result->setUrls(std::move(list));
}
return result;
}
MimeImageData ReadMimeImage(not_null<const QMimeData*> data) {
if (data->hasFormat(u"application/x-td-use-jpeg"_q)) {
auto bytes = data->data(u"image/jpeg"_q);
auto read = Images::Read({ .content = bytes });
if (read.format == "jpeg" && !read.image.isNull()) {
return {
.image = std::move(read.image),
.content = std::move(bytes),
};
}
} else if (data->hasImage()) {
return { .image = qvariant_cast<QImage>(data->imageData()) };
}
return {};
}
} // namespace Core

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QString>
#include <QtCore/QStringList>
#include <QtCore/QMimeType>
#include <QtGui/QImage>
class QMimeData;
@@ -52,4 +53,17 @@ private:
[[nodiscard]] std::shared_ptr<QMimeData> ShareMimeMediaData(
not_null<const QMimeData*> original);
struct MimeImageData {
QImage image;
QByteArray content;
[[nodiscard]] bool empty() const {
return image.isNull();
}
explicit operator bool() const {
return !empty();
}
};
[[nodiscard]] MimeImageData ReadMimeImage(not_null<const QMimeData*> data);
} // namespace Core

View File

@@ -25,19 +25,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/invoke_queued.h"
#include "base/qthelp_url.h"
#include "base/qthelp_regex.h"
#include "base/qt/qt_common_adapters.h"
#include "ui/ui_utility.h"
#include "ui/effects/animations.h"
#include <QtCore/QLockFile>
#include <QtGui/QSessionManager>
#include <QtGui/QScreen>
#include <QtGui/qpa/qplatformscreen.h>
namespace Core {
namespace {
constexpr auto kEmptyPidForCommandResponse = 0ULL;
QChar _toHex(ushort v) {
v = v & 0x000F;
return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v));
@@ -134,7 +132,7 @@ int Sandbox::start() {
[=] { socketDisconnected(); });
connect(
&_localSocket,
base::QLocalSocket_error,
&QLocalSocket::errorOccurred,
[=](QLocalSocket::LocalSocketError error) { socketError(error); });
connect(
&_localSocket,
@@ -210,38 +208,52 @@ void Sandbox::launchApplication() {
}
void Sandbox::setupScreenScale() {
const auto dpi = Sandbox::primaryScreen()->logicalDotsPerInch();
LOG(("Primary screen DPI: %1").arg(dpi));
if (dpi <= 108) {
cSetScreenScale(100); // 100%: 96 DPI (0-108)
} else if (dpi <= 132) {
cSetScreenScale(125); // 125%: 120 DPI (108-132)
} else if (dpi <= 168) {
cSetScreenScale(150); // 150%: 144 DPI (132-168)
} else if (dpi <= 216) {
cSetScreenScale(200); // 200%: 192 DPI (168-216)
} else if (dpi <= 264) {
cSetScreenScale(250); // 250%: 240 DPI (216-264)
} else {
cSetScreenScale(300); // 300%: 288 DPI (264-inf)
}
const auto ratio = devicePixelRatio();
if (ratio > 1.) {
if (!Platform::IsMac() || (ratio != 2.)) {
LOG(("Found non-trivial Device Pixel Ratio: %1").arg(ratio));
LOG(("Environmental variables: QT_DEVICE_PIXEL_RATIO='%1'").arg(qEnvironmentVariable("QT_DEVICE_PIXEL_RATIO")));
LOG(("Environmental variables: QT_SCALE_FACTOR='%1'").arg(qEnvironmentVariable("QT_SCALE_FACTOR")));
LOG(("Environmental variables: QT_AUTO_SCREEN_SCALE_FACTOR='%1'").arg(qEnvironmentVariable("QT_AUTO_SCREEN_SCALE_FACTOR")));
LOG(("Environmental variables: QT_SCREEN_SCALE_FACTORS='%1'").arg(qEnvironmentVariable("QT_SCREEN_SCALE_FACTORS")));
}
style::SetDevicePixelRatio(std::ceil(ratio));
if (Platform::IsMac() && ratio == 2.) {
cSetScreenScale(110); // 110% for Retina screens by default.
} else {
cSetScreenScale(style::kScaleDefault);
LOG(("Global devicePixelRatio: %1").arg(ratio));
const auto logEnv = [](const char *name) {
const auto value = qEnvironmentVariable(name);
if (!value.isEmpty()) {
LOG(("%1: %2").arg(name).arg(value));
}
};
logEnv("QT_DEVICE_PIXEL_RATIO");
logEnv("QT_AUTO_SCREEN_SCALE_FACTOR");
logEnv("QT_ENABLE_HIGHDPI_SCALING");
logEnv("QT_SCALE_FACTOR");
logEnv("QT_SCREEN_SCALE_FACTORS");
logEnv("QT_SCALE_FACTOR_ROUNDING_POLICY");
logEnv("QT_DPI_ADJUSTMENT_POLICY");
logEnv("QT_USE_PHYSICAL_DPI");
logEnv("QT_FONT_DPI");
// Like Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor.
// Round up for .75 and higher. This favors "small UI" over "large UI".
const auto roundedRatio = ((ratio - qFloor(ratio)) < 0.75)
? qFloor(ratio)
: qCeil(ratio);
const auto useRatio = std::clamp(roundedRatio, 1, 3);
style::SetDevicePixelRatio(useRatio);
const auto screen = Sandbox::primaryScreen();
const auto dpi = screen->logicalDotsPerInch();
const auto basePair = screen->handle()->logicalBaseDpi();
const auto base = (basePair.first + basePair.second) * 0.5;
const auto screenScaleExact = dpi / base;
const auto screenScale = int(base::SafeRound(screenScaleExact * 4)) * 25;
LOG(("Primary screen DPI: %1, Base: %2.").arg(dpi).arg(base));
LOG(("Computed screen scale: %1").arg(screenScale));
if (Platform::IsMac()) {
// 110% for Retina screens by default.
cSetScreenScale((useRatio == 2) ? 110 : 100);
} else {
const auto clamped = std::clamp(
screenScale * useRatio,
50 * useRatio,
300);
cSetScreenScale(int(base::SafeRound(clamped * 1. / useRatio)));
}
LOG(("DevicePixelRatio: %1").arg(useRatio));
LOG(("ScreenScale: %1").arg(cScreenScale()));
}
Sandbox::~Sandbox() = default;
@@ -298,17 +310,21 @@ void Sandbox::socketReading() {
return;
}
_localSocketReadData.append(_localSocket.readAll());
if (QRegularExpression("RES:(\\d+);").match(_localSocketReadData).hasMatch()) {
uint64 pid = base::StringViewMid(
_localSocketReadData,
4,
_localSocketReadData.length() - 5).toULongLong();
if (pid != kEmptyPidForCommandResponse) {
psActivateProcess(pid);
}
LOG(("Show command response received, pid = %1, activating and quitting...").arg(pid));
return Quit();
const auto m = QRegularExpression(u"RES:(\\d+)_(\\d+);"_q).match(
_localSocketReadData);
if (!m.hasMatch()) {
return;
}
const auto processId = m.capturedView(1).toULongLong();
const auto windowId = m.capturedView(2).toULongLong();
if (windowId) {
Platform::ActivateOtherProcess(processId, windowId);
}
LOG(("Show command response received, processId = %1, windowId = %2, "
"activating and quitting..."
).arg(processId
).arg(windowId));
return Quit();
}
void Sandbox::socketError(QLocalSocket::LocalSocketError e) {
@@ -426,8 +442,9 @@ void Sandbox::readClients() {
for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; to = (from < l) ? cmds.indexOf(QChar(';'), from) : -1) {
auto cmd = base::StringViewMid(cmds, from, to - from);
if (cmd.startsWith(u"CMD:"_q)) {
execExternal(cmds.mid(from + 4, to - from - 4));
const auto response = u"RES:%1;"_q.arg(QApplication::applicationPid()).toLatin1();
const auto processId = QApplication::applicationPid();
const auto windowId = execExternal(cmds.mid(from + 4, to - from - 4));
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
i->first->write(response.data(), response.size());
} else if (cmd.startsWith(u"SEND:"_q)) {
if (cSendPaths().isEmpty()) {
@@ -437,14 +454,12 @@ void Sandbox::readClients() {
qputenv("XDG_ACTIVATION_TOKEN", _escapeFrom7bit(cmds.mid(from + 21, to - from - 21)).toUtf8());
} else if (cmd.startsWith(u"OPEN:"_q)) {
startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192);
auto activateRequired = StartUrlRequiresActivate(startUrl);
if (activateRequired) {
execExternal("show");
}
const auto responsePid = activateRequired
? QApplication::applicationPid()
: kEmptyPidForCommandResponse;
const auto response = u"RES:%1;"_q.arg(responsePid).toLatin1();
const auto activationRequired = StartUrlRequiresActivate(startUrl);
const auto processId = QApplication::applicationPid();
const auto windowId = activationRequired
? execExternal("show")
: 0;
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
i->first->write(response.data(), response.size());
} else {
LOG(("Sandbox Error: unknown command %1 passed in local socket").arg(cmd.toString()));
@@ -643,17 +658,21 @@ void Sandbox::closeApplication() {
_updateChecker = nullptr;
}
void Sandbox::execExternal(const QString &cmd) {
uint64 Sandbox::execExternal(const QString &cmd) {
DEBUG_LOG(("Sandbox Info: executing external command '%1'").arg(cmd));
if (cmd == "show") {
if (Core::IsAppLaunched() && Core::App().primaryWindow()) {
Core::App().primaryWindow()->activate();
} else if (PreLaunchWindow::instance()) {
PreLaunchWindow::instance()->activate();
if (Core::IsAppLaunched() && Core::App().activePrimaryWindow()) {
const auto window = Core::App().activePrimaryWindow();
window->activate();
return Platform::ActivationWindowId(window->widget());
} else if (const auto window = PreLaunchWindow::instance()) {
window->activate();
return Platform::ActivationWindowId(window);
}
} else if (cmd == "quit") {
Quit();
}
return 0;
}
} // namespace Core

View File

@@ -95,7 +95,9 @@ private:
void singleInstanceChecked();
void launchApplication();
void setupScreenScale();
void execExternal(const QString &cmd);
// Return window id for activation.
uint64 execExternal(const QString &cmd);
// Single instance application
void socketConnected();

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/platform_specific.h"
#include "base/parse_helper.h"
#include <QAction>
#include <QShortcut>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
@@ -143,11 +144,13 @@ public:
void fill();
void clear();
[[nodiscard]] std::vector<Command> lookup(int shortcutId) const;
[[nodiscard]] std::vector<Command> lookup(
not_null<QObject*> object) const;
void toggleMedia(bool toggled);
void toggleSupport(bool toggled);
void listen(not_null<QWidget*> widget);
const QStringList &errors() const;
[[nodiscard]] const QStringList &errors() const;
private:
void fillDefaults();
@@ -156,15 +159,15 @@ private:
void set(const QString &keys, Command command, bool replace = false);
void remove(const QString &keys);
void unregister(base::unique_qptr<QShortcut> shortcut);
void unregister(base::unique_qptr<QAction> shortcut);
QStringList _errors;
base::flat_map<QKeySequence, base::unique_qptr<QShortcut>> _shortcuts;
base::flat_multi_map<int, Command> _commandByShortcutId;
base::flat_map<QKeySequence, base::unique_qptr<QAction>> _shortcuts;
base::flat_multi_map<not_null<QObject*>, Command> _commandByObject;
base::flat_set<QShortcut*> _mediaShortcuts;
base::flat_set<QShortcut*> _supportShortcuts;
base::flat_set<QAction*> _mediaShortcuts;
base::flat_set<QAction*> _supportShortcuts;
};
@@ -227,7 +230,7 @@ void Manager::fill() {
void Manager::clear() {
_errors.clear();
_shortcuts.clear();
_commandByShortcutId.clear();
_commandByObject.clear();
_mediaShortcuts.clear();
_supportShortcuts.clear();
}
@@ -236,11 +239,11 @@ const QStringList &Manager::errors() const {
return _errors;
}
std::vector<Command> Manager::lookup(int shortcutId) const {
std::vector<Command> Manager::lookup(not_null<QObject*> object) const {
auto result = std::vector<Command>();
auto i = _commandByShortcutId.findFirst(shortcutId);
const auto end = _commandByShortcutId.end();
for (; i != end && (i->first == shortcutId); ++i) {
auto i = _commandByObject.findFirst(object);
const auto end = _commandByObject.end();
for (; i != end && (i->first == object); ++i) {
result.push_back(i->second);
}
return result;
@@ -258,6 +261,12 @@ void Manager::toggleSupport(bool toggled) {
}
}
void Manager::listen(not_null<QWidget*> widget) {
for (const auto &[keys, shortcut] : _shortcuts) {
widget->addAction(shortcut.get());
}
}
bool Manager::readCustomFile() {
// read custom shortcuts from file if it exists or write an empty custom shortcuts file
QFile file(CustomFilePath());
@@ -370,6 +379,9 @@ void Manager::fillDefaults() {
set(u"ctrl+3"_q, Command::ChatPinned3);
set(u"ctrl+4"_q, Command::ChatPinned4);
set(u"ctrl+5"_q, Command::ChatPinned5);
set(u"ctrl+6"_q, Command::ChatPinned6);
set(u"ctrl+7"_q, Command::ChatPinned7);
set(u"ctrl+8"_q, Command::ChatPinned8);
auto &&folders = ranges::views::zip(
kShowFolder,
@@ -409,10 +421,10 @@ void Manager::writeDefaultFile() {
shortcuts.push_back(version);
for (const auto &[sequence, shortcut] : _shortcuts) {
const auto shortcutId = shortcut->id();
auto i = _commandByShortcutId.findFirst(shortcutId);
const auto end = _commandByShortcutId.end();
for (; i != end && i->first == shortcutId; ++i) {
const auto object = shortcut.get();
auto i = _commandByObject.findFirst(object);
const auto end = _commandByObject.end();
for (; i != end && i->first == object; ++i) {
const auto j = CommandNames.find(i->second);
if (j != CommandNames.end()) {
QJsonObject entry;
@@ -438,12 +450,9 @@ void Manager::set(const QString &keys, Command command, bool replace) {
_errors.push_back(u"Could not derive key sequence '%1'!"_q.arg(keys));
return;
}
auto shortcut = base::make_unique_q<QShortcut>(
result,
Core::App().primaryWindow()->widget().get(),
nullptr,
nullptr,
Qt::ApplicationShortcut);
auto shortcut = base::make_unique_q<QAction>();
shortcut->setShortcut(result);
shortcut->setShortcutContext(Qt::ApplicationShortcut);
if (!AutoRepeatCommands.contains(command)) {
shortcut->setAutoRepeat(false);
}
@@ -452,20 +461,16 @@ void Manager::set(const QString &keys, Command command, bool replace) {
if (isMediaShortcut || isSupportShortcut) {
shortcut->setEnabled(false);
}
auto id = shortcut->id();
auto object = shortcut.get();
auto i = _shortcuts.find(result);
if (i == end(_shortcuts)) {
i = _shortcuts.emplace(result, std::move(shortcut)).first;
} else if (replace) {
unregister(std::exchange(i->second, std::move(shortcut)));
} else {
id = i->second->id();
object = i->second.get();
}
if (!id) {
_errors.push_back(u"Could not create shortcut '%1'!"_q.arg(keys));
return;
}
_commandByShortcutId.emplace(id, command);
_commandByObject.emplace(object, command);
if (!shortcut && isMediaShortcut) {
_mediaShortcuts.emplace(i->second.get());
}
@@ -491,9 +496,9 @@ void Manager::remove(const QString &keys) {
}
}
void Manager::unregister(base::unique_qptr<QShortcut> shortcut) {
void Manager::unregister(base::unique_qptr<QAction> shortcut) {
if (shortcut) {
_commandByShortcutId.erase(shortcut->id());
_commandByObject.erase(shortcut.get());
_mediaShortcuts.erase(shortcut.get());
_supportShortcuts.erase(shortcut.get());
}
@@ -557,8 +562,10 @@ const QStringList &Errors() {
return Data.errors();
}
bool HandleEvent(not_null<QShortcutEvent*> event) {
return Launch(Data.lookup(event->shortcutId()));
bool HandleEvent(
not_null<QObject*> object,
not_null<QShortcutEvent*> event) {
return Launch(Data.lookup(object));
}
void ToggleMediaShortcuts(bool toggled) {
@@ -573,4 +580,8 @@ void Finish() {
Data.clear();
}
void Listen(not_null<QWidget*> widget) {
Data.listen(widget);
}
} // namespace Shortcuts

View File

@@ -34,6 +34,9 @@ enum class Command {
ChatPinned3,
ChatPinned4,
ChatPinned5,
ChatPinned6,
ChatPinned7,
ChatPinned8,
ShowAllChats,
ShowFolder1,
@@ -97,8 +100,10 @@ rpl::producer<not_null<Request*>> Requests();
void Start();
void Finish();
void Listen(not_null<QWidget*> widget);
bool Launch(Command command);
bool HandleEvent(not_null<QShortcutEvent*> event);
bool HandleEvent(not_null<QObject*> object, not_null<QShortcutEvent*> event);
const QStringList &Errors();

View File

@@ -124,7 +124,7 @@ QString UiIntegration::angleBackendFilePath() {
}
void UiIntegration::textActionsUpdated() {
if (const auto window = Core::App().primaryWindow()) {
if (const auto window = Core::App().activeWindow()) {
window->widget()->updateGlobalMenu();
}
}

View File

@@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "base/bytes.h"
#include "base/unixtime.h"
#include "base/qt/qt_common_adapters.h"
#include "storage/localstorage.h"
#include "core/application.h"
#include "core/changelogs.h"
@@ -664,7 +663,7 @@ void HttpChecker::start() {
_reply->connect(_reply, &QNetworkReply::finished, [=] {
gotResponse();
});
_reply->connect(_reply, base::QNetworkReply_error, [=](auto e) {
_reply->connect(_reply, &QNetworkReply::errorOccurred, [=](auto e) {
gotFailure(e);
});
}
@@ -703,7 +702,7 @@ void HttpChecker::clearSentRequest() {
return;
}
reply->disconnect(reply, &QNetworkReply::finished, nullptr, nullptr);
reply->disconnect(reply, base::QNetworkReply_error, nullptr, nullptr);
reply->disconnect(reply, &QNetworkReply::errorOccurred, nullptr, nullptr);
reply->abort();
reply->deleteLater();
_manager = nullptr;
@@ -857,7 +856,7 @@ void HttpLoaderActor::sendRequest() {
&HttpLoaderActor::partFinished);
connect(
_reply.get(),
base::QNetworkReply_error,
&QNetworkReply::errorOccurred,
this,
&HttpLoaderActor::partFailed);
connect(
@@ -1650,7 +1649,7 @@ void UpdateApplication() {
} else {
cSetAutoUpdate(true);
const auto window = Core::IsAppLaunched()
? Core::App().primaryWindow()
? Core::App().activePrimaryWindow()
: nullptr;
if (window) {
if (const auto controller = window->sessionController()) {

View File

@@ -7,11 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "logs.h"
#include "base/basic_types.h"
#include "base/flags.h"
#include "base/algorithm.h"
#include "base/assertion.h"
#include "base/bytes.h"
#include <crl/crl_time.h>
@@ -177,57 +175,3 @@ inline int ceilclamp(float64 value, int32 step, int32 lowest, int32 highest) {
lowest,
highest);
}
static int32 FullArcLength = 360 * 16;
static int32 QuarterArcLength = (FullArcLength / 4);
static int32 MinArcLength = (FullArcLength / 360);
static int32 AlmostFullArcLength = (FullArcLength - MinArcLength);
// 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>
class NeverFreedPointer {
public:
NeverFreedPointer() = default;
NeverFreedPointer(const NeverFreedPointer<T> &other) = delete;
NeverFreedPointer &operator=(const NeverFreedPointer<T> &other) = delete;
template <typename... Args>
void createIfNull(Args&&... args) {
if (isNull()) {
reset(new T(std::forward<Args>(args)...));
}
};
T *data() const {
return _p;
}
T *release() {
return base::take(_p);
}
void reset(T *p = nullptr) {
delete _p;
_p = p;
}
bool isNull() const {
return data() == nullptr;
}
void clear() {
reset();
}
T *operator->() const {
return data();
}
T &operator*() const {
Assert(!isNull());
return *data();
}
explicit operator bool() const {
return !isNull();
}
private:
T *_p;
};

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 4004003;
constexpr auto AppVersionStr = "4.4.3";
constexpr auto AppVersion = 4005009;
constexpr auto AppVersionStr = "4.5.9";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -7,11 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_abstract_structure.h"
#include "base/never_freed_pointer.h"
namespace Data {
namespace {
using DataStructures = OrderedSet<AbstractStructure**>;
NeverFreedPointer<DataStructures> structures;
base::NeverFreedPointer<DataStructures> structures;
} // namespace

View File

@@ -279,11 +279,11 @@ void LoadCloudFile(
if (const auto onstack = progress) {
onstack();
}
}, [=, &file](bool started) {
}, [=, &file](FileLoader::Error error) {
finish(file);
file.flags |= CloudFile::Flag::Failed;
if (const auto onstack = fail) {
onstack(started);
onstack(error.started);
}
}, [=, &file] {
finish(file);

View File

@@ -988,8 +988,9 @@ void DocumentData::handleLoaderUpdates() {
_loader->updates(
) | rpl::start_with_next_error_done([=] {
_owner->documentLoadProgress(this);
}, [=](bool started) {
if (started && _loader) {
}, [=](FileLoader::Error error) {
using FailureReason = FileLoader::FailureReason;
if (error.started && _loader) {
const auto origin = _loader->fileOrigin();
const auto failedFileName = _loader->fileName();
const auto retry = [=] {
@@ -1000,23 +1001,21 @@ void DocumentData::handleLoaderUpdates() {
tr::lng_download_finish_failed(),
crl::guard(&session(), retry)
}));
} else {
// Sometimes we have LOCATION_INVALID error in documents / stickers.
// Sometimes FILE_REFERENCE_EXPIRED could not be handled.
//
//const auto openSettings = [=] {
// Core::App().settings().etDownloadPathBookmark(QByteArray());
// Core::App().settings().setDownloadPath(QString());
// Ui::show(Box<DownloadPathBox>());
//};
//Ui::show(Box<Ui::ConfirmBox>(
// tr::lng_download_path_failed(tr::now),
// tr::lng_download_path_settings(tr::now),
// crl::guard(&session(), openSettings)));
} else if (error.failureReason == FailureReason::FileWriteFailure) {
if (!Core::App().settings().downloadPath().isEmpty()) {
Core::App().settings().setDownloadPathBookmark(QByteArray());
Core::App().settings().setDownloadPath(QString());
Core::App().saveSettingsDelayed();
InvokeQueued(qApp, [] {
Ui::show(
Ui::MakeInformBox(
tr::lng_download_path_failed(tr::now)));
});
}
}
finishLoad();
status = FileDownloadFailed;
_owner->documentLoadFail(this, started);
_owner->documentLoadFail(this, error.started);
}, [=] {
finishLoad();
_owner->documentLoadDone(this);
@@ -1198,26 +1197,29 @@ bool DocumentData::isStickerSetInstalled() const {
Image *DocumentData::getReplyPreview(
Data::FileOrigin origin,
not_null<PeerData*> context) {
not_null<PeerData*> context,
bool spoiler) {
if (!hasThumbnail()) {
return nullptr;
} else if (!_replyPreview) {
_replyPreview = std::make_unique<Data::ReplyPreview>(this);
}
return _replyPreview->image(origin, context);
return _replyPreview->image(origin, context, spoiler);
}
Image *DocumentData::getReplyPreview(not_null<HistoryItem*> item) {
return getReplyPreview(item->fullId(), item->history()->peer);
const auto media = item->media();
const auto spoiler = media && media->hasSpoiler();
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
}
bool DocumentData::replyPreviewLoaded() const {
bool DocumentData::replyPreviewLoaded(bool spoiler) const {
if (!hasThumbnail()) {
return true;
} else if (!_replyPreview) {
return false;
}
return _replyPreview->loaded();
return _replyPreview->loaded(spoiler);
}
StickerData *DocumentData::sticker() const {

View File

@@ -142,9 +142,10 @@ public:
[[nodiscard]] Image *getReplyPreview(
Data::FileOrigin origin,
not_null<PeerData*> context);
not_null<PeerData*> context,
bool spoiler);
[[nodiscard]] Image *getReplyPreview(not_null<HistoryItem*> item);
[[nodiscard]] bool replyPreviewLoaded() const;
[[nodiscard]] bool replyPreviewLoaded(bool spoiler) const;
[[nodiscard]] StickerData *sticker() const;
[[nodiscard]] Data::FileOrigin stickerSetOrigin() const;

View File

@@ -286,10 +286,6 @@ void ResolveDocument(
|| document->isVoiceMessage()
|| document->isVideoMessage()) {
::Media::Player::instance()->playPause({ document, msgId });
} else if (item
&& document->isAnimation()
&& HistoryView::Gif::CanPlayInline(document)) {
document->owner().requestAnimationPlayInline(item);
} else {
showDocument();
}

View File

@@ -508,9 +508,13 @@ HistoryItem *DownloadManager::lookupLoadingItem(
void DownloadManager::loadingStopWithConfirmation(
Fn<void()> callback,
Main::Session *onlyInSession) {
const auto window = Core::App().primaryWindow();
const auto item = lookupLoadingItem(onlyInSession);
if (!window || !item) {
if (!item) {
return;
}
const auto window = Core::App().windowFor(
&item->history()->session().account());
if (!window) {
return;
}
const auto weak = base::make_weak(&item->history()->session());

View File

@@ -152,7 +152,7 @@ void DocumentOpenWithClickHandler::Open(
data->saveFromDataSilent();
const auto path = data->filepath(true);
if (!path.isEmpty()) {
File::OpenWith(path, QCursor::pos());
File::OpenWith(path);
} else {
DocumentSaveClickHandler::Save(
origin,

View File

@@ -618,7 +618,7 @@ Image *MediaPhoto::replyPreview() const {
}
bool MediaPhoto::replyPreviewLoaded() const {
return _photo->replyPreviewLoaded();
return _photo->replyPreviewLoaded(_spoiler);
}
TextWithEntities MediaPhoto::notificationText() const {
@@ -854,7 +854,7 @@ Image *MediaFile::replyPreview() const {
}
bool MediaFile::replyPreviewLoaded() const {
return _document->replyPreviewLoaded();
return _document->replyPreviewLoaded(_spoiler);
}
ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
@@ -1323,7 +1323,7 @@ TextForMimeData MediaLocation::clipboardText() const {
if (!descriptionResult.text.isEmpty()) {
result.append(std::move(descriptionResult));
}
result.append(LocationClickHandler(_point).dragText());
result.append(LocationClickHandler(_point).url());
return result;
}
@@ -1479,10 +1479,11 @@ Image *MediaWebPage::replyPreview() const {
}
bool MediaWebPage::replyPreviewLoaded() const {
const auto spoiler = false;
if (const auto document = MediaWebPage::document()) {
return document->replyPreviewLoaded();
return document->replyPreviewLoaded(spoiler);
} else if (const auto photo = MediaWebPage::photo()) {
return photo->replyPreviewLoaded();
return photo->replyPreviewLoaded(spoiler);
}
return true;
}
@@ -1552,10 +1553,11 @@ Image *MediaGame::replyPreview() const {
}
bool MediaGame::replyPreviewLoaded() const {
const auto spoiler = false;
if (const auto document = _game->document) {
return document->replyPreviewLoaded();
return document->replyPreviewLoaded(spoiler);
} else if (const auto photo = _game->photo) {
return photo->replyPreviewLoaded();
return photo->replyPreviewLoaded(spoiler);
}
return true;
}
@@ -1675,8 +1677,9 @@ Image *MediaInvoice::replyPreview() const {
}
bool MediaInvoice::replyPreviewLoaded() const {
const auto spoiler = false;
if (const auto photo = _invoice.photo) {
return photo->replyPreviewLoaded();
return photo->replyPreviewLoaded(spoiler);
}
return true;
}

View File

@@ -514,7 +514,8 @@ bool ChannelHasActiveCall(not_null<ChannelData*> channel) {
rpl::producer<QImage> PeerUserpicImageValue(
not_null<PeerData*> peer,
int size) {
int size,
std::optional<int> radius) {
return [=](auto consumer) {
auto result = rpl::lifetime();
struct State {
@@ -541,7 +542,10 @@ rpl::producer<QImage> PeerUserpicImageValue(
}
state->key = key;
state->empty = false;
consumer.put_next(peer->generateUserpicImage(state->view, size));
consumer.put_next(peer->generateUserpicImage(
state->view,
size,
radius));
};
peer->session().changes().peerFlagsValue(
peer,

View File

@@ -133,7 +133,8 @@ inline auto PeerFullFlagValue(
[[nodiscard]] rpl::producer<QImage> PeerUserpicImageValue(
not_null<PeerData*> peer,
int size);
int size,
std::optional<int> radius = {});
[[nodiscard]] const AllowedReactions &PeerAllowedReactions(
not_null<PeerData*> peer);

View File

@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kPhotoSideLimit = 1280;
constexpr auto kPhotoSideLimit = 2560;
using Data::PhotoMedia;
using Data::PhotoSize;
@@ -209,22 +209,25 @@ bool PhotoData::uploading() const {
Image *PhotoData::getReplyPreview(
Data::FileOrigin origin,
not_null<PeerData*> context) {
not_null<PeerData*> context,
bool spoiler) {
if (!_replyPreview) {
_replyPreview = std::make_unique<Data::ReplyPreview>(this);
}
return _replyPreview->image(origin, context);
return _replyPreview->image(origin, context, spoiler);
}
Image *PhotoData::getReplyPreview(not_null<HistoryItem*> item) {
return getReplyPreview(item->fullId(), item->history()->peer);
const auto media = item->media();
const auto spoiler = media && media->hasSpoiler();
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
}
bool PhotoData::replyPreviewLoaded() const {
bool PhotoData::replyPreviewLoaded(bool spoiler) const {
if (!_replyPreview) {
return false;
}
return _replyPreview->loaded();
return _replyPreview->loaded(spoiler);
}
void PhotoData::setRemoteLocation(

View File

@@ -66,9 +66,10 @@ public:
[[nodiscard]] Image *getReplyPreview(
Data::FileOrigin origin,
not_null<PeerData*> context);
not_null<PeerData*> context,
bool spoiler);
[[nodiscard]] Image *getReplyPreview(not_null<HistoryItem*> item);
[[nodiscard]] bool replyPreviewLoaded() const;
[[nodiscard]] bool replyPreviewLoaded(bool spoiler) const;
void setRemoteLocation(
int32 dc,

View File

@@ -18,6 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/file_download.h"
#include "ui/image/image.h"
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
namespace Data {
PhotoMedia::PhotoMedia(not_null<PhotoData*> owner)
@@ -101,6 +104,14 @@ void PhotoMedia::set(
QImage image,
QByteArray bytes) {
const auto index = PhotoSizeIndex(size);
const auto limit = PhotoData::SideLimit();
if (image.width() > limit || image.height() > limit) {
image = image.scaled(
limit,
limit,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
}
_images[index] = PhotoImage{
.data = std::make_unique<Image>(std::move(image)),
.bytes = std::move(bytes),
@@ -190,8 +201,7 @@ bool PhotoMedia::saveToFile(const QString &path) {
QFile f(path);
return f.open(QIODevice::WriteOnly)
&& (f.write(video) == video.size());
} else if (const auto photo = imageBytes(large)
; !photo.isEmpty()) {
} else if (const auto photo = imageBytes(large); !photo.isEmpty()) {
QFile f(path);
return f.open(QIODevice::WriteOnly)
&& (f.write(photo) == photo.size());
@@ -202,4 +212,24 @@ bool PhotoMedia::saveToFile(const QString &path) {
return false;
}
bool PhotoMedia::setToClipboard() {
constexpr auto large = PhotoSize::Large;
if (const auto video = videoContent(large); !video.isEmpty()) {
return false;
}
auto fallback = image(large)->original();
if (fallback.isNull()) {
return false;
}
const auto bytes = imageBytes(large);
auto mime = std::make_unique<QMimeData>();
mime->setImageData(std::move(fallback));
if (auto bytes = imageBytes(large); !bytes.isEmpty()) {
mime->setData(u"image/jpeg"_q, std::move(bytes));
}
mime->setData(u"application/x-td-use-jpeg"_q, "1");
QGuiApplication::clipboard()->setMimeData(mime.release());
return true;
}
} // namespace Data

View File

@@ -48,6 +48,7 @@ public:
void collectLocalData(not_null<PhotoMedia*> local);
bool saveToFile(const QString &path);
bool setToClipboard();
private:
struct PhotoImage {

View File

@@ -27,7 +27,11 @@ ReplyPreview::ReplyPreview(not_null<PhotoData*> photo)
ReplyPreview::~ReplyPreview() = default;
void ReplyPreview::prepare(not_null<Image*> image, Images::Options options) {
void ReplyPreview::prepare(
not_null<Image*> image,
Images::Options options,
bool spoiler) {
using namespace Images;
if (image->isNull()) {
return;
}
@@ -41,24 +45,34 @@ void ReplyPreview::prepare(not_null<Image*> image, Images::Options options) {
: QSize(
st::msgReplyBarSize.height(),
h * st::msgReplyBarSize.height() / w);
thumbSize *= cIntRetinaFactor();
options |= Images::Option::TransparentBackground;
thumbSize *= style::DevicePixelRatio();
options |= Option::TransparentBackground;
auto outerSize = st::msgReplyBarSize.height();
auto bitmap = image->pixNoCache(
thumbSize,
{ .options = options, .outer = { outerSize, outerSize } });
_image = std::make_unique<Image>(bitmap.toImage());
_good = ((options & Images::Option::Blur) == 0);
auto original = spoiler
? image->original().scaled(
{ 40, 40 },
Qt::KeepAspectRatio,
Qt::SmoothTransformation)
: image->original();
auto prepared = Prepare(std::move(original), thumbSize, {
.options = options | (spoiler ? Option::Blur : Option()),
.outer = { outerSize, outerSize },
});
(spoiler ? _spoilered : _regular) = std::make_unique<Image>(
std::move(prepared));
_good = spoiler || ((options & Option::Blur) == 0);
}
Image *ReplyPreview::image(
Data::FileOrigin origin,
not_null<PeerData*> context) {
if (_checked) {
return _image.get();
}
if (_document) {
if (!_image || (!_good && _document->hasThumbnail())) {
not_null<PeerData*> context,
bool spoiler) {
auto &image = spoiler ? _spoilered : _regular;
auto &checked = spoiler ? _checkedSpoilered : _checkedRegular;
if (checked) {
return image.get();
} else if (_document) {
if (!image || (!_good && _document->hasThumbnail())) {
if (!_documentMedia) {
_documentMedia = _document->createMediaView();
_documentMedia->thumbnailWanted(origin);
@@ -67,51 +81,66 @@ Image *ReplyPreview::image(
const auto option = _document->isVideoMessage()
? Images::Option::RoundCircle
: Images::Option::None;
if (thumbnail) {
if (spoiler) {
if (const auto image = _documentMedia->thumbnailInline()) {
prepare(image, option, true);
} else if (thumbnail) {
prepare(thumbnail, option, true);
}
} else if (thumbnail) {
prepare(thumbnail, option);
} else if (!_image) {
} else if (!image) {
if (const auto image = _documentMedia->thumbnailInline()) {
prepare(image, option | Images::Option::Blur);
}
}
if (_good || !_document->hasThumbnail()) {
_checked = true;
checked = true;
_documentMedia = nullptr;
}
}
} else {
Assert(_photo != nullptr);
if (!_image || !_good) {
if (!image || !_good) {
const auto inlineThumbnailBytes = _photo->inlineThumbnailBytes();
if (!_photoMedia) {
_photoMedia = _photo->createMediaView();
}
using Size = PhotoSize;
const auto loadThumbnail = inlineThumbnailBytes.isEmpty()
|| _photoMedia->autoLoadThumbnailAllowed(context);
|| (!spoiler
&& _photoMedia->autoLoadThumbnailAllowed(context));
if (loadThumbnail) {
_photoMedia->wanted(PhotoSize::Small, origin);
_photoMedia->wanted(Size::Small, origin);
}
if (const auto small = _photoMedia->image(PhotoSize::Small)) {
prepare(small, Images::Option(0));
} else if (const auto large = _photoMedia->image(
PhotoSize::Large)) {
prepare(large, Images::Option(0));
} else if (!_image) {
if (spoiler) {
if (const auto blurred = _photoMedia->thumbnailInline()) {
prepare(blurred, {}, true);
} else if (const auto small = _photoMedia->image(Size::Small)) {
prepare(small, {}, true);
} else if (const auto large = _photoMedia->image(Size::Large)) {
prepare(large, {}, true);
}
} else if (const auto small = _photoMedia->image(Size::Small)) {
prepare(small, {});
} else if (const auto large = _photoMedia->image(Size::Large)) {
prepare(large, {});
} else if (!image) {
if (const auto blurred = _photoMedia->thumbnailInline()) {
prepare(blurred, Images::Option::Blur);
}
}
if (_good) {
_checked = true;
checked = true;
_photoMedia = nullptr;
}
}
}
return _image.get();
return image.get();
}
bool ReplyPreview::loaded() const {
return _checked;
bool ReplyPreview::loaded(bool spoiler) const {
return spoiler ? _checkedSpoilered : _checkedRegular;
}
} // namespace Data

View File

@@ -25,19 +25,25 @@ public:
[[nodiscard]] Image *image(
Data::FileOrigin origin,
not_null<PeerData*> context);
[[nodiscard]] bool loaded() const;
not_null<PeerData*> context,
bool spoiler);
[[nodiscard]] bool loaded(bool spoiler) const;
private:
void prepare(not_null<Image*> image, Images::Options options);
void prepare(
not_null<Image*> image,
Images::Options options,
bool spoiler = false);
std::unique_ptr<Image> _image;
std::unique_ptr<Image> _regular;
std::unique_ptr<Image> _spoilered;
PhotoData *_photo = nullptr;
DocumentData *_document = nullptr;
std::shared_ptr<PhotoMedia> _photoMedia;
std::shared_ptr<DocumentMedia> _documentMedia;
bool _good = false;
bool _checked = false;
bool _checkedRegular = false;
bool _checkedSpoilered = false;
};

View File

@@ -805,6 +805,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
const auto canViewMembers = channel->canViewMembers();
const auto canAddMembers = channel->canAddMembers();
const auto wasCallNotEmpty = Data::ChannelHasActiveCall(channel);
if (const auto count = data.vparticipants_count()) {
channel->setMembersCount(count->v);
}
@@ -912,6 +914,9 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|| canAddMembers != channel->canAddMembers()) {
flags |= UpdateFlag::Rights;
}
if (wasCallNotEmpty != Data::ChannelHasActiveCall(channel)) {
flags |= UpdateFlag::GroupCall;
}
}, [&](const MTPDchannelForbidden &data) {
const auto channel = result->asChannel();
@@ -1695,29 +1700,12 @@ void Session::requestItemTextRefresh(not_null<HistoryItem*> item) {
}
}
void Session::requestAnimationPlayInline(not_null<HistoryItem*> item) {
_animationPlayInlineRequest.fire_copy(item);
if (const auto media = item->media()) {
if (const auto data = media->document()) {
if (data && data->isVideoMessage()) {
const auto msgId = item->fullId();
::Media::Player::instance()->playPause({ data, msgId });
}
}
}
}
void Session::requestUnreadReactionsAnimation(not_null<HistoryItem*> item) {
enumerateItemViews(item, [&](not_null<ViewElement*> view) {
view->animateUnreadReactions();
});
}
rpl::producer<not_null<HistoryItem*>> Session::animationPlayInlineRequest() const {
return _animationPlayInlineRequest.events();
}
rpl::producer<not_null<const HistoryItem*>> Session::itemRemoved() const {
return _itemRemoved.events();
}

View File

@@ -275,9 +275,7 @@ public:
void requestItemViewRefresh(not_null<HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> itemViewRefreshRequest() const;
void requestItemTextRefresh(not_null<HistoryItem*> item);
void requestAnimationPlayInline(not_null<HistoryItem*> item);
void requestUnreadReactionsAnimation(not_null<HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> animationPlayInlineRequest() const;
void notifyHistoryUnloaded(not_null<const History*> history);
[[nodiscard]] rpl::producer<not_null<const History*>> historyUnloaded() const;
void notifyItemDataChange(not_null<HistoryItem*> item);
@@ -861,7 +859,6 @@ private:
rpl::event_stream<not_null<HistoryItem*>> _itemViewRefreshRequest;
rpl::event_stream<not_null<HistoryItem*>> _itemTextRefreshRequest;
rpl::event_stream<not_null<HistoryItem*>> _itemDataChanges;
rpl::event_stream<not_null<HistoryItem*>> _animationPlayInlineRequest;
rpl::event_stream<not_null<const HistoryItem*>> _itemRemoved;
rpl::event_stream<not_null<const ViewElement*>> _viewRemoved;
rpl::event_stream<not_null<const History*>> _historyUnloaded;

View File

@@ -144,6 +144,8 @@ WebPageType ParseWebPageType(
return WebPageType::Video;
} else if (type == u"photo"_q) {
return WebPageType::Photo;
} else if (type == u"document"_q) {
return WebPageType::Document;
} else if (type == u"profile"_q) {
return WebPageType::Profile;
} else if (type == u"telegram_background"_q) {

View File

@@ -26,6 +26,7 @@ enum class WebPageType {
Photo,
Video,
Document,
User,
Bot,

View File

@@ -926,7 +926,8 @@ void Stickers::specialSetReceived(
LOG(("API Error: "
"received recent attached stickers hash %1 "
"while counted hash is %2"
).arg(hash, counted));
).arg(hash
).arg(counted));
}
session().local().writeRecentMasks();
} break;

View File

@@ -475,8 +475,8 @@ editTopicMaxHeight: 408px;
chooseTopicListItem: PeerListItem(defaultPeerListItem) {
height: 44px;
photoSize: 28px;
photoPosition: point(8px, 5px);
photoSize: 20px;
photoPosition: point(16px, 12px);
namePosition: point(55px, 11px);
nameStyle: TextStyle(defaultTextStyle) {
font: font(14px semibold);

View File

@@ -70,7 +70,7 @@ namespace {
constexpr auto kHashtagResultsLimit = 5;
constexpr auto kStartReorderThreshold = 30;
base::options::toggle TabbedPanelShowOnClick({
base::options::toggle CtrlClickChatNewWindow({
.id = kOptionCtrlClickChatNewWindow,
.name = "New chat window by Ctrl+Click",
.description = "Open chat in a new window by Ctrl+Click "
@@ -2851,7 +2851,6 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
_searchInChat = key;
_searchFromPeer = from;
if (_searchInChat) {
_controller->closeFolder();
onHashtagFilterUpdate(QStringView());
_cancelSearchInChat->show();
} else {
@@ -3311,7 +3310,7 @@ bool InnerWidget::chooseRow(
const auto modifyChosenRow = [](
ChosenRow row,
Qt::KeyboardModifiers modifiers) {
if (TabbedPanelShowOnClick.value()) {
if (CtrlClickChatNewWindow.value()) {
row.newWindow = (modifiers & Qt::ControlModifier);
}
return row;
@@ -3739,6 +3738,9 @@ void InnerWidget::setupShortcuts() {
Command::ChatPinned3,
Command::ChatPinned4,
Command::ChatPinned5,
Command::ChatPinned6,
Command::ChatPinned7,
Command::ChatPinned8,
};
auto &&pinned = ranges::views::zip(
kPinned,
@@ -3791,14 +3793,14 @@ void InnerWidget::setupShortcuts() {
});
request->check(Command::ReadChat) && request->handle([=] {
const auto history = _selected ? _selected->history() : nullptr;
if (history) {
if (history->chatListBadgesState().unread) {
session().data().histories().readInbox(history);
}
return true;
const auto thread = _selected ? _selected->thread() : nullptr;
if (!thread) {
return false;
}
return (history != nullptr);
if (Window::IsUnreadThread(thread)) {
Window::MarkAsReadThread(thread);
}
return true;
});
request->check(Command::ShowContacts) && request->handle([=] {

View File

@@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_entry.h"
#include "dialogs/ui/dialogs_layout.h"
#include "data/data_session.h"
#include "mainwidget.h"
namespace Dialogs {

View File

@@ -91,19 +91,19 @@ constexpr auto kNoneLayer = 0;
style::al_center);
constexpr auto kPenWidth = 1.5;
constexpr auto kAngleStart = 90 * 16;
constexpr auto kAngleSpan = 180 * 16;
const auto penWidth = style::ConvertScaleExact(kPenWidth);
auto pen = QPen(st::premiumButtonFg);
pen.setJoinStyle(Qt::RoundJoin);
pen.setCapStyle(Qt::RoundCap);
pen.setWidthF(style::ConvertScaleExact(kPenWidth));
pen.setWidthF(penWidth);
q.setPen(pen);
q.setBrush(Qt::NoBrush);
q.drawArc(innerRect, kAngleStart, kAngleSpan);
q.drawArc(innerRect, arc::kQuarterLength, arc::kHalfLength);
q.setClipRect(innerRect - QMargins(innerRect.width() / 2, 0, 0, 0));
q.setClipRect(innerRect
- QMargins(innerRect.width() / 2, 0, -penWidth, -penWidth));
pen.setStyle(Qt::DotLine);
q.setPen(pen);
q.drawEllipse(innerRect);
@@ -252,6 +252,10 @@ Row::Row(Key key, int index, int top) : _id(key), _top(top), _index(index) {
}
}
Row::~Row() {
clearTopicJumpRipple();
}
void Row::recountHeight(float64 narrowRatio) {
if (const auto history = _id.history()) {
_height = history->isForum()
@@ -487,6 +491,11 @@ void Row::stopLastRipple() {
}
}
void Row::clearRipple() {
BasicRow::clearRipple();
clearTopicJumpRipple();
}
void Row::addTopicJumpRipple(
QPoint origin,
not_null<Ui::TopicJumpCache*> topicJumpCache,
@@ -503,10 +512,15 @@ void Row::addTopicJumpRipple(
}
void Row::clearTopicJumpRipple() {
if (_topicJumpRipple) {
clearRipple();
_topicJumpRipple = 0;
if (!_topicJumpRipple) {
return;
}
const auto history = this->history();
const auto view = history ? &history->lastItemDialogsView() : nullptr;
if (view) {
view->clearRipple();
}
_topicJumpRipple = 0;
}
bool Row::topicJumpRipple() const {

View File

@@ -53,11 +53,11 @@ public:
void addRipple(QPoint origin, QSize size, Fn<void()> updateCallback);
virtual void stopLastRipple();
virtual void clearRipple();
void addRippleWithMask(
QPoint origin,
QImage mask,
Fn<void()> updateCallback);
void clearRipple();
void paintRipple(
QPainter &p,
@@ -82,6 +82,7 @@ public:
explicit Row(std::nullptr_t) {
}
Row(Key key, int index, int top);
~Row();
[[nodiscard]] int top() const {
return _top;
@@ -105,6 +106,7 @@ public:
[[nodiscard]] bool lookupIsInTopicJump(int x, int y) const;
void stopLastRipple() override;
void clearRipple() override;
void addTopicJumpRipple(
QPoint origin,
not_null<Ui::TopicJumpCache*> topicJumpCache,

View File

@@ -188,7 +188,12 @@ Widget::Widget(
+ st::defaultDialogRow.photoSize
+ st::defaultDialogRow.padding.left())
, _searchControls(this)
, _mainMenuToggle(_searchControls, st::dialogsMenuToggle)
, _mainMenu({
.toggle = object_ptr<Ui::IconButton>(
_searchControls,
st::dialogsMenuToggle),
.under = object_ptr<Ui::AbstractButton>(_searchControls),
})
, _searchForNarrowFilters(_searchControls, st::dialogsSearchForNarrowFilters)
, _filter(_searchControls, st::dialogsFilter, tr::lng_dlg_filter())
, _chooseFromUser(
@@ -427,6 +432,14 @@ Widget::Widget(
updateControlsGeometry();
}, lifetime());
_childListShown.changes(
) | rpl::filter((rpl::mappers::_1 == 0.) || (rpl::mappers::_1 == 1.)
) | rpl::start_with_next([=](float64 shown) {
const auto color = (shown > 0.) ? &st::dialogsRippleBg : nullptr;
_mainMenu.toggle->setRippleColorOverride(color);
_searchForNarrowFilters->setRippleColorOverride(color);
}, lifetime());
setupDownloadBar();
}
}
@@ -662,13 +675,18 @@ void Widget::setupSupportMode() {
}
void Widget::setupMainMenuToggle() {
_mainMenuToggle->setClickedCallback([=] { showMainMenu(); });
_mainMenu.under->setClickedCallback([=] {
_mainMenu.toggle->clicked({}, Qt::LeftButton);
});
_mainMenu.under->stackUnder(_mainMenu.toggle);
_mainMenu.toggle->setClickedCallback([=] { showMainMenu(); });
rpl::single(rpl::empty) | rpl::then(
controller()->filtersMenuChanged()
) | rpl::start_with_next([=] {
const auto filtersHidden = !controller()->filtersWidth();
_mainMenuToggle->setVisible(filtersHidden);
_mainMenu.toggle->setVisible(filtersHidden);
_mainMenu.under->setVisible(filtersHidden);
_searchForNarrowFilters->setVisible(!filtersHidden);
updateControlsGeometry();
}, lifetime());
@@ -680,8 +698,8 @@ void Widget::setupMainMenuToggle() {
: !state.allMuted
? &st::dialogsMenuToggleUnread
: &st::dialogsMenuToggleUnreadMuted;
_mainMenuToggle->setIconOverride(icon, icon);
}, _mainMenuToggle->lifetime());
_mainMenu.toggle->setIconOverride(icon, icon);
}, _mainMenu.toggle->lifetime());
}
void Widget::setupShortcuts() {
@@ -1463,9 +1481,7 @@ void Widget::showMainMenu() {
controller()->widget()->showMainMenu();
}
void Widget::searchMessages(
const QString &query,
Key inChat) {
void Widget::searchMessages(const QString &query, Key inChat) {
if (_childList) {
const auto forum = controller()->shownForum().current();
const auto topic = inChat.topic();
@@ -1476,18 +1492,27 @@ void Widget::searchMessages(
}
hideChildList();
}
if (_openedFolder) {
controller()->closeFolder();
}
const auto inChatChanged = [&] {
const auto inPeer = inChat.peer();
const auto inTopic = inChat.topic();
if (!inTopic && _openedForum && inPeer == _openedForum->channel()) {
if (!inTopic
&& _openedForum
&& inPeer == _openedForum->channel()
&& _subsectionTopBar
&& _subsectionTopBar->searchMode()) {
return false;
} else if ((inTopic || (inPeer && !inPeer->isForum()))
&& (inChat == _searchInChat)) {
return false;
} else if (const auto inPeer = inChat.peer()) {
if (inPeer->migrateTo() == _searchInChat.peer()
&& !_searchInChat.topic()) {
return false;
if (const auto to = inPeer->migrateTo()) {
if (to == _searchInChat.peer() && !_searchInChat.topic()) {
return false;
}
}
}
return true;
@@ -2112,18 +2137,7 @@ void Widget::closeChildList(anim::type animated) {
}
void Widget::searchInChat(Key chat) {
if (_openedForum && !chat.peer()->forum()) {
controller()->closeForum();
}
if (_openedFolder) {
if (_childList && _childList->setSearchInChat(chat)) {
return;
}
controller()->closeFolder();
}
cancelSearch();
setSearchInChat(chat);
applyFilterUpdate(true);
searchMessages(QString(), chat);
}
bool Widget::setSearchInChat(Key chat, PeerData *from) {
@@ -2160,15 +2174,13 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
}
}
_searchInMigrated = nullptr;
if (peer) {
if (peer && !forum) {
if (_layout != Layout::Main) {
return false;
} else if (const auto migrateTo = peer->migrateTo()) {
return setSearchInChat(peer->owner().history(migrateTo), from);
} else if (const auto migrateFrom = peer->migrateFrom()) {
if (!forum) {
_searchInMigrated = peer->owner().history(migrateFrom);
}
_searchInMigrated = peer->owner().history(migrateFrom);
}
}
if (searchInPeerUpdated) {
@@ -2180,6 +2192,9 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
updateSearchFromVisibility();
clearSearchCache();
}
if (_searchInChat && _layout == Layout::Main) {
controller()->closeFolder();
}
_inner->searchInChat(_searchInChat, _searchFromAuthor);
if (_subsectionTopBar) {
_subsectionTopBar->searchEnableJumpToDate(
@@ -2368,7 +2383,7 @@ void Widget::updateControlsGeometry() {
auto filterLeft = (controller()->filtersWidth()
? st::dialogsFilterSkip
: (st::dialogsFilterPadding.x() + _mainMenuToggle->width()))
: (st::dialogsFilterPadding.x() + _mainMenu.toggle->width()))
+ st::dialogsFilterPadding.x();
auto filterRight = (session().domain().local().hasLocalPasscode()
? (st::dialogsFilterPadding.x() + _lockUnlock->width())
@@ -2388,9 +2403,16 @@ void Widget::updateControlsGeometry() {
_filter->setGeometryToLeft(filterLeft, filterTop, filterWidth, _filter->height());
auto mainMenuLeft = anim::interpolate(
st::dialogsFilterPadding.x(),
(_narrowWidth - _mainMenuToggle->width()) / 2,
(_narrowWidth - _mainMenu.toggle->width()) / 2,
narrowRatio);
_mainMenuToggle->moveToLeft(mainMenuLeft, st::dialogsFilterPadding.y());
_mainMenu.toggle->moveToLeft(mainMenuLeft, st::dialogsFilterPadding.y());
_mainMenu.under->setGeometry(
0,
0,
filterLeft,
_mainMenu.toggle->y()
+ _mainMenu.toggle->height()
+ st::dialogsFilterPadding.y());
const auto searchLeft = anim::interpolate(
-_searchForNarrowFilters->width(),
(_narrowWidth - _searchForNarrowFilters->width()) / 2,

View File

@@ -33,6 +33,7 @@ class ContactStatus;
} // namespace HistoryView
namespace Ui {
class AbstractButton;
class IconButton;
class PopupMenu;
class DropdownMenu;
@@ -222,7 +223,10 @@ private:
int _narrowWidth = 0;
object_ptr<Ui::RpWidget> _searchControls;
object_ptr<HistoryView::TopBarWidget> _subsectionTopBar = { nullptr } ;
object_ptr<Ui::IconButton> _mainMenuToggle;
struct {
object_ptr<Ui::IconButton> toggle;
object_ptr<Ui::AbstractButton> under;
} _mainMenu;
object_ptr<Ui::IconButton> _searchForNarrowFilters;
object_ptr<Ui::InputField> _filter;
object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _chooseFromUser;

View File

@@ -227,6 +227,12 @@ void MessageView::stopLastRipple() {
}
}
void MessageView::clearRipple() {
if (_topics) {
_topics->clearRipple();
}
}
int MessageView::countWidth() const {
auto result = 0;
if (!_senderCache.isEmpty()) {

View File

@@ -73,6 +73,7 @@ public:
not_null<TopicJumpCache*> topicJumpCache,
Fn<void()> updateCallback);
void stopLastRipple();
void clearRipple();
private:
struct LoadingContext;

View File

@@ -187,6 +187,10 @@ void TopicsView::stopLastRipple() {
}
}
void TopicsView::clearRipple() {
_ripple = nullptr;
}
void TopicsView::paintRipple(
QPainter &p,
int x,

View File

@@ -89,8 +89,8 @@ public:
int y,
int outerWidth,
const QColor *colorOverride) const;
void clearRipple();
void stopLastRipple();
void clearRipple();
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;

View File

@@ -54,8 +54,8 @@ photoEditorRotateButton: IconButton(defaultIconButton) {
width: photoEditorButtonBarHeight;
height: photoEditorButtonBarHeight;
icon: icon {{ "photo_editor/rotate", photoEditorButtonIconFg }};
iconOver: icon {{ "photo_editor/rotate", photoEditorButtonIconFgOver }};
icon: icon {{ "photo_editor/rotate-flip_horizontal", photoEditorButtonIconFg }};
iconOver: icon {{ "photo_editor/rotate-flip_horizontal", photoEditorButtonIconFgOver }};
rippleAreaPosition: point(4px, 4px);
rippleAreaSize: 40px;

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h" // InformBox
#include "editor/editor_layer_widget.h"
#include "editor/photo_editor.h"
#include "storage/localimageloader.h"
#include "storage/storage_media_prepare.h"
#include "ui/chat/attach/attach_prepare.h"
#include "window/window_controller.h"
@@ -42,10 +43,11 @@ void OpenWithPreparedFile(
return;
}
const auto sideLimit = PhotoSideLimit();
auto callback = [=, done = std::move(doneCallback)](
const PhotoModifications &mods) {
image->modifications = mods;
Storage::UpdateImageDetails(*file, previewWidth);
Storage::UpdateImageDetails(*file, previewWidth, sideLimit);
{
using namespace Ui;
const auto size = file->preview.size();

View File

@@ -490,8 +490,6 @@ void ApiWrap::requestSplitRanges() {
_splits.push_back(MTP_messageRange(
MTP_int(1),
MTP_int(std::numeric_limits<int>::max())));
} else {
ranges::reverse(_splits);
}
_startProcess->splitIndex = useOnlyLastSplit()
? (_splits.size() - 1)

View File

@@ -294,14 +294,6 @@ InnerWidget::InnerWidget(
view->itemDataChanged();
}
}, lifetime());
session().data().animationPlayInlineRequest(
) | rpl::start_with_next([=](auto item) {
if (const auto view = viewForItem(item)) {
if (const auto media = view->media()) {
media->playAnimation();
}
}
}, lifetime());
session().data().itemVisibilityQueries(
) | rpl::filter([=](
const Data::Session::ItemVisibilityQuery &query) {
@@ -1362,9 +1354,7 @@ void InnerWidget::copyContextImage(not_null<PhotoData*> photo) {
if (photo->isNull() || !media || !media->loaded()) {
return;
}
const auto image = media->image(Data::PhotoSize::Large)->original();
QGuiApplication::clipboard()->setImage(image);
media->setToClipboard();
}
void InnerWidget::copySelectedText() {

View File

@@ -845,18 +845,17 @@ not_null<HistoryItem*> History::addNewToBack(
addItemToBlock(item);
if (!unread && item->isRegular()) {
if (const auto sharedMediaTypes = item->sharedMediaTypes()) {
if (const auto types = item->sharedMediaTypes()) {
auto from = loadedAtTop() ? 0 : minMsgId();
auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
auto &storage = session().storage();
storage.add(Storage::SharedMediaAddExisting(
peer->id,
MsgId(0), // topicRootId
sharedMediaTypes,
types,
item->id,
{ from, till }));
const auto pinned = sharedMediaTypes.test(
Storage::SharedMediaType::Pinned);
const auto pinned = types.test(Storage::SharedMediaType::Pinned);
if (pinned) {
setHasPinnedMessages(true);
}
@@ -864,7 +863,7 @@ not_null<HistoryItem*> History::addNewToBack(
storage.add(Storage::SharedMediaAddExisting(
peer->id,
topic->rootId(),
sharedMediaTypes,
types,
item->id,
{ item->id, item->id}));
if (pinned) {

View File

@@ -2638,8 +2638,7 @@ void HistoryInner::copyContextImage(
if (photo->isNull() || !media || !media->loaded()) {
return;
} else if (!showCopyMediaRestriction(item)) {
const auto image = media->image(Data::PhotoSize::Large)->original();
QGuiApplication::clipboard()->setImage(image);
media->setToClipboard();
}
}

View File

@@ -1395,6 +1395,7 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) {
setReplyMarkup(base::take(edition.replyMarkup));
}
if (!isLocalUpdateMedia()) {
removeFromSharedMediaIndex();
refreshMedia(edition.mtpMedia);
}
if (!edition.useSameReactions) {
@@ -1405,6 +1406,9 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) {
setText(_media
? edition.textWithEntities
: EnsureNonEmpty(edition.textWithEntities));
if (!isLocalUpdateMedia()) {
indexAsNewItem();
}
if (!edition.useSameReplies) {
if (!edition.replies.isNull) {
if (checkRepliesPts(edition.replies)) {
@@ -1424,6 +1428,7 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) {
if (message.vaction().type() == mtpc_messageActionHistoryClear) {
const auto wasGrouped = history()->owner().groups().isGrouped(this);
setReplyMarkup({});
removeFromSharedMediaIndex();
refreshMedia(nullptr);
setTextValue({});
changeViewsCount(-1);
@@ -1653,7 +1658,10 @@ void HistoryItem::destroyHistoryEntry() {
Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const {
auto result = Storage::SharedMediaTypesMask {};
if (const auto media = this->media()) {
const auto media = _savedLocalEditMediaData
? _savedLocalEditMediaData->media.get()
: _media.get();
if (media) {
result.set(media->sharedMediaTypes());
}
if (hasTextLinks()) {
@@ -1684,6 +1692,18 @@ void HistoryItem::indexAsNewItem() {
}
}
void HistoryItem::removeFromSharedMediaIndex() {
if (isRegular()) {
if (const auto types = sharedMediaTypes()) {
_history->session().storage().remove(
Storage::SharedMediaRemoveOne(
_history->peer->id,
types,
id));
}
}
}
void HistoryItem::incrementReplyToTopCounter() {
if (const auto reply = Get<HistoryMessageReply>()) {
changeReplyToTopCounter(reply, 1);
@@ -3676,18 +3696,22 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
const auto duration = (period == 5)
? u"5 seconds"_q
: Ui::FormatTTL(period);
if (const auto from = action.vauto_setting_from()) {
if (const auto from = action.vauto_setting_from(); from && period) {
if (const auto peer = _from->owner().peer(peerFromUser(*from))) {
if (!peer->isSelf() && period) {
result.text = tr::lng_action_ttl_global(
result.text = (peer->id == peer->session().userPeerId())
? tr::lng_action_ttl_global_me(
tr::now,
lt_duration,
{ .text = duration },
Ui::Text::WithEntities)
: tr::lng_action_ttl_global(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::Link(peer->name(), 1), // Link 1.
lt_duration,
{ .text = duration },
Ui::Text::WithEntities);
return result;
}
return result;
}
}
if (isPost()) {

View File

@@ -346,6 +346,7 @@ public:
[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const;
void indexAsNewItem();
void removeFromSharedMediaIndex();
[[nodiscard]] QString notificationHeader() const;
[[nodiscard]] TextWithEntities notificationText() const;

View File

@@ -47,7 +47,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_";
constexpr auto kReplyBarAlpha = 230. / 255.;
} // namespace
@@ -283,9 +282,10 @@ bool HistoryMessageReply::updateData(
}
if (replyToMsg) {
const auto repaint = [=] { holder->customEmojiRepaint(); };
const auto context = Core::MarkedTextContext{
.session = &holder->history()->session(),
.customEmojiRepaint = [=] { holder->customEmojiRepaint(); },
.customEmojiRepaint = repaint,
};
replyToText.setMarkedText(
st::messageTextStyle,
@@ -312,9 +312,17 @@ bool HistoryMessageReply::updateData(
? replyToMsg->from()->id
: PeerId(0);
}
const auto media = replyToMsg->media();
if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) {
spoiler = nullptr;
} else if (!spoiler) {
spoiler = std::make_unique<Ui::SpoilerAnimation>(repaint);
}
} else if (force) {
replyToMsgId = 0;
replyToColorKey = PeerId(0);
spoiler = nullptr;
}
if (force) {
holder->history()->owner().requestItemResize(holder);
@@ -456,21 +464,22 @@ void HistoryMessageReply::paint(
st::msgReplyBarSize.height(),
w + 2 * x);
const auto opacity = p.opacity();
p.setOpacity(opacity * kReplyBarAlpha);
p.setOpacity(opacity * kBarAlpha);
p.fillRect(rbar, bar);
p.setOpacity(opacity);
}
if (w > st::msgReplyBarSkip) {
if (replyToMsg) {
auto hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false;
const auto media = replyToMsg->media();
auto hasPreview = media && media->hasReplyPreview();
if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) {
hasPreview = false;
}
auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
if (hasPreview) {
if (const auto image = replyToMsg->media()->replyPreview()) {
if (const auto image = media->replyPreview()) {
auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x);
const auto preview = image->pixSingle(
image->size() / style::DevicePixelRatio(),
@@ -482,6 +491,16 @@ void HistoryMessageReply::paint(
.outer = to.size(),
});
p.drawPixmap(to.x(), to.y(), preview);
if (spoiler) {
holder->clearCustomEmojiRepaint();
Ui::FillSpoilerRect(
p,
to,
Ui::DefaultImageSpoiler().frame(
spoiler->index(
context.now,
context.paused)));
}
}
}
if (w > st::msgReplyBarSkip + previewSkip) {

View File

@@ -195,6 +195,8 @@ struct HistoryMessageReply
Expects(replyToVia == nullptr);
}
static constexpr auto kBarAlpha = 230. / 255.;
bool updateData(not_null<HistoryItem*> holder, bool force = false);
// Must be called before destructor.
@@ -246,6 +248,7 @@ struct HistoryMessageReply
WebPageId replyToWebPageId = 0;
ReplyToMessagePointer replyToMsg;
std::unique_ptr<HistoryMessageVia> replyToVia;
std::unique_ptr<Ui::SpoilerAnimation> spoiler;
ClickHandlerPtr replyToLnk;
mutable Ui::Text::String replyToName, replyToText;
mutable int replyToVersion = 0;

View File

@@ -24,6 +24,10 @@ public:
return QString();
}
QString url() const override {
return _text;
}
QString dragText() const override {
return _text;
}

View File

@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_permissions_box.h" // ShowAboutGigagroup.
#include "boxes/peers/edit_peer_requests_box.h"
#include "core/file_utilities.h"
#include "core/mime_type.h"
#include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h"
#include "ui/emoji_config.h"
@@ -126,7 +127,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/player/media_player_instance.h"
#include "core/application.h"
#include "apiwrap.h"
#include "base/options.h"
#include "base/qthelp_regex.h"
#include "ui/boxes/report_box.h"
#include "ui/chat/pinned_bar.h"
@@ -171,13 +171,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QWindow>
#include <QtCore/QMimeData>
const char kOptionAutoScrollInactiveChat[] =
"auto-scroll-inactive-chat";
namespace {
constexpr auto kMessagesPerPageFirst = 10;
constexpr auto kMessagesPerPage = 10;
constexpr auto kMessagesPerPageFirst = 30;
constexpr auto kMessagesPerPage = 50;
constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request
constexpr auto kScrollToVoiceAfterScrolledMs = 1000;
constexpr auto kSkipRepaintWhileScrollMs = 100;
@@ -194,13 +191,6 @@ constexpr auto kCommonModifiers = 0
| Qt::ControlModifier;
const auto kPsaAboutPrefix = "cloud_lng_about_psa_";
base::options::toggle AutoScrollInactiveChat({
.id = kOptionAutoScrollInactiveChat,
.name = "Enable auto-scroll of inactive chat",
.description = "Enable auto-scrolling chat for new messages, "
"even when the window is not in focus.",
});
[[nodiscard]] rpl::producer<PeerData*> ActivePeerValue(
not_null<Window::SessionController*> controller) {
return controller->activeChatValue(
@@ -563,15 +553,6 @@ HistoryWidget::HistoryWidget(
});
}, lifetime());
session().data().animationPlayInlineRequest(
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
if (const auto view = item->mainView()) {
if (const auto media = view->media()) {
media->playAnimation();
}
}
}, lifetime());
session().data().webPageUpdates(
) | rpl::filter([=](not_null<WebPageData*> page) {
return (_previewData == page.get());
@@ -742,8 +723,9 @@ HistoryWidget::HistoryWidget(
if (flags & PeerUpdateFlag::UnavailableReason) {
const auto unavailable = _peer->computeUnavailableReason();
if (!unavailable.isEmpty()) {
const auto account = &_peer->account();
closeCurrent();
if (const auto primary = Core::App().primaryWindow()) {
if (const auto primary = Core::App().windowFor(account)) {
primary->show(Ui::MakeInformBox(unavailable));
}
return;
@@ -1971,7 +1953,9 @@ void HistoryWidget::applyCloudDraft(History *history) {
}
bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
if (session().supportMode()) {
Expects(_history != nullptr);
if (session().supportMode() || !_history->trackUnreadMessages()) {
return true;
} else if (!_historyInited) {
return false;
@@ -1979,6 +1963,14 @@ bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
_history->calculateFirstUnreadMessage();
const auto unread = _history->firstUnreadMessage();
const auto visibleBottom = _scroll->scrollTop() + _scroll->height();
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
"unread: %4, top: %5, visibleBottom: %6."
).arg(_history->peer->name()
).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom())
).arg(unread ? unread->data()->id.bare : 0
).arg(unread ? _list->itemTop(unread) : -1
).arg(visibleBottom));
return unread && _list->itemTop(unread) <= visibleBottom;
}
@@ -2004,6 +1996,11 @@ void HistoryWidget::showHistory(
if (showAtMsgId == ShowAtUnreadMsgId
&& insideJumpToEndInsteadOfToUnread()) {
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
"Jumping to end instead of unread."
).arg(_history->peer->name()
).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom())));
showAtMsgId = ShowAtTheEndMsgId;
} else if (showAtMsgId == ShowForChooseMessagesMsgId) {
if (_chooseForReport) {
@@ -2023,6 +2020,11 @@ void HistoryWidget::showHistory(
}
const auto canShowNow = _history->isReadyFor(showAtMsgId);
if (!canShowNow) {
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Showing delayed at %4."
).arg(_history->peer->name()
).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom())
).arg(showAtMsgId.bare));
delayedShowAt(showAtMsgId);
} else {
_history->forgetScrollState();
@@ -2042,6 +2044,13 @@ void HistoryWidget::showHistory(
setMsgId(showAtMsgId);
if (_historyInited) {
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
"Showing instant at %4."
).arg(_history->peer->name()
).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom())
).arg(showAtMsgId.bare));
const auto to = countInitialScrollTop();
const auto item = getItemFromHistoryOrMigrated(
_showAtMsgId);
@@ -2179,6 +2188,11 @@ void HistoryWidget::showHistory(
} else {
_chooseForReport = nullptr;
}
if (_showAtMsgId == ShowAtUnreadMsgId
&& !_history->trackUnreadMessages()
&& !hasSavedScroll()) {
_showAtMsgId = ShowAtTheEndMsgId;
}
refreshTopBarActiveChat();
updateTopBarSelection();
@@ -2966,11 +2980,12 @@ void HistoryWidget::unreadCountUpdated() {
crl::on_main(this, [=, history = _history] {
if (history == _history) {
closeCurrent();
_cancelRequests.fire({});
}
});
} else {
_cornerButtons.updateJumpDownVisibility(_history->isForum()
const auto hideCounter = _history->isForum()
|| !_history->trackUnreadMessages();
_cornerButtons.updateJumpDownVisibility(hideCounter
? 0
: _history->chatListBadgesState().unreadCounter);
}
@@ -2994,7 +3009,7 @@ void HistoryWidget::messagesFailed(const MTP::Error &error, int requestId) {
|| error.type() == u"USER_BANNED_IN_CHANNEL"_q) {
auto was = _peer;
closeCurrent();
if (const auto primary = Core::App().primaryWindow()) {
if (const auto primary = Core::App().windowFor(&was->account())) {
Ui::ShowMultilineToast({
.parentOverride = Window::Show(primary).toastParent(),
.text = { (was && was->isMegagroup())
@@ -3266,6 +3281,12 @@ void HistoryWidget::loadMessages() {
const auto minId = 0;
const auto historyHash = uint64(0);
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Loading up before %4."
).arg(_history->peer->name()
).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom())
).arg(offsetId.bare));
const auto history = from;
const auto type = Data::Histories::RequestType::History;
auto &histories = history->owner().histories();
@@ -3318,6 +3339,12 @@ void HistoryWidget::loadMessagesDown() {
const auto minId = 0;
const auto historyHash = uint64(0);
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Loading down after %4."
).arg(_history->peer->name()
).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom())
).arg(offsetId.bare));
const auto history = from;
const auto type = Data::Histories::RequestType::History;
auto &histories = history->owner().histories();
@@ -3350,6 +3377,12 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
clearAllLoadRequests();
_delayedShowAtMsgId = showAtMsgId;
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Loading delayed around %4."
).arg(_history->peer->name()
).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom())
).arg(showAtMsgId.bare));
auto from = _history;
auto offsetId = MsgId();
auto offset = 0;
@@ -4028,6 +4061,10 @@ void HistoryWidget::doneShow() {
void HistoryWidget::cornerButtonsShowAtPosition(
Data::MessagePosition position) {
if (position == Data::UnreadMessagePosition) {
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Show at unread requested."
).arg(_history->peer->name()
).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom())));
showHistory(_peer->id, ShowAtUnreadMsgId);
} else if (_peer && position.fullId.peer == _peer->id) {
showHistory(_peer->id, position.fullId.msg);
@@ -4499,39 +4536,52 @@ bool HistoryWidget::updateCmdStartShown() {
}
void HistoryWidget::searchInChat() {
if (_history) {
controller()->content()->searchInChat(_history);
}
}
void HistoryWidget::searchInChatEmbedded(std::optional<QString> query) {
if (!_history) {
return;
} else if (controller()->isPrimary()) {
controller()->content()->searchInChat(_history);
} else if (!_composeSearch) {
const auto search = [=] {
const auto update = [=] {
updateControlsVisibility();
updateBotKeyboard();
updateFieldPlaceholder();
updateControlsGeometry();
};
_composeSearch = std::make_unique<HistoryView::ComposeSearch>(
this,
controller(),
_history);
update();
setInnerFocus();
_composeSearch->destroyRequests(
) | rpl::take(
1
) | rpl::start_with_next([=] {
_composeSearch = nullptr;
update();
setInnerFocus();
}, _composeSearch->lifetime());
};
if (!preventsClose(search)) {
search();
} else if (_composeSearch) {
if (query) {
_composeSearch->setQuery(*query);
}
_composeSearch->setInnerFocus();
return;
}
const auto search = crl::guard(_list, [=] {
if (!_history) {
return;
}
const auto update = [=] {
updateControlsVisibility();
updateBotKeyboard();
updateFieldPlaceholder();
updateControlsGeometry();
};
_composeSearch = std::make_unique<HistoryView::ComposeSearch>(
this,
controller(),
_history,
query.value_or(QString()));
update();
setInnerFocus();
_composeSearch->destroyRequests(
) | rpl::take(
1
) | rpl::start_with_next([=] {
_composeSearch = nullptr;
update();
setInnerFocus();
}, _composeSearch->lifetime());
});
if (!preventsClose(search)) {
search();
}
}
@@ -5144,16 +5194,13 @@ bool HistoryWidget::confirmSendingFiles(
}
}
if (hasImage) {
auto image = qvariant_cast<QImage>(data->imageData());
if (!image.isNull()) {
confirmSendingFiles(
std::move(image),
QByteArray(),
overrideSendImagesAsPhotos,
insertTextOnCancel);
return true;
}
if (auto read = Core::ReadMimeImage(data)) {
confirmSendingFiles(
std::move(read.image),
std::move(read.content),
overrideSendImagesAsPhotos,
insertTextOnCancel);
return true;
}
return false;
}
@@ -5348,8 +5395,15 @@ MsgId HistoryWidget::replyToId() const {
return _replyToId ? _replyToId : (_kbReplyTo ? _kbReplyTo->id : 0);
}
bool HistoryWidget::hasSavedScroll() const {
Expects(_history != nullptr);
return _history->scrollTopItem
|| (_migrated && _migrated->scrollTopItem);
}
int HistoryWidget::countInitialScrollTop() {
if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem)) {
if (hasSavedScroll()) {
return _list->historyScrollTop();
} else if (_showAtMsgId
&& (IsServerMsgId(_showAtMsgId)
@@ -5413,9 +5467,6 @@ int HistoryWidget::countAutomaticScrollTop() {
Expects(_list != nullptr);
if (const auto unread = _history->firstUnreadMessage()) {
if (AutoScrollInactiveChat.value()) {
return ScrollMax;
}
const auto firstUnreadTop = _list->itemTop(unread);
const auto possibleUnreadBarTop = _scroll->scrollTopMax()
+ HistoryView::UnreadBar::height()
@@ -5866,7 +5917,10 @@ std::optional<bool> HistoryWidget::cornerButtonsDownShown() {
}
const auto haveUnreadBelowBottom = [&](History *history) {
if (!_list || !history || history->unreadCount() <= 0) {
if (!_list
|| !history
|| history->unreadCount() <= 0
|| !history->trackUnreadMessages()) {
return false;
}
const auto unread = history->firstUnreadMessage();
@@ -5922,7 +5976,6 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
e->ignore();
} else if (e->key() == Qt::Key_Back) {
controller()->showBackFromStack();
_cancelRequests.fire({});
} else if (e->key() == Qt::Key_PageDown) {
_scroll->keyPressEvent(e);
@@ -6224,7 +6277,7 @@ void HistoryWidget::checkPinnedBarState() {
_pinnedBar = std::make_unique<Ui::PinnedBar>(this, [=] {
return controller()->isGifPausedAtLeastFor(
Window::GifPauseReason::Any);
});
}, controller()->gifPauseLevelChanged());
auto pinnedRefreshed = Info::Profile::SharedMediaCountValue(
_peer,
MsgId(0), // topicRootId
@@ -7490,20 +7543,44 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
p.setInactive(
controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any));
p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg);
const auto media = (!drawWebPagePreview && drawMsgText)
? drawMsgText->media()
: nullptr;
const auto hasPreview = media && media->hasReplyPreview();
const auto preview = hasPreview ? media->replyPreview() : nullptr;
const auto spoilered = preview && media->hasSpoiler();
if (!spoilered) {
_replySpoiler = nullptr;
} else if (!_replySpoiler) {
_replySpoiler = std::make_unique<Ui::SpoilerAnimation>([=] {
updateField();
});
}
if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
const auto now = crl::now();
const auto paused = p.inactive();
auto replyLeft = st::historyReplySkip;
(_editMsgId ? st::historyEditIcon : st::historyReplyIcon).paint(p, st::historyReplyIconPosition + QPoint(0, backy), width());
if (!drawWebPagePreview) {
if (drawMsgText) {
if (drawMsgText->media() && drawMsgText->media()->hasReplyPreview()) {
if (const auto image = drawMsgText->media()->replyPreview()) {
if (hasPreview) {
if (preview) {
auto to = QRect(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
p.drawPixmap(to.x(), to.y(), image->pixSingle(
image->size() / style::DevicePixelRatio(),
p.drawPixmap(to.x(), to.y(), preview->pixSingle(
preview->size() / style::DevicePixelRatio(),
{
.options = Images::Option::RoundSmall,
.outer = to.size(),
}));
if (_replySpoiler) {
Ui::FillSpoilerRect(
p,
to,
Ui::DefaultImageSpoiler().frame(
_replySpoiler->index(now, paused)));
}
}
replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
}
@@ -7521,8 +7598,8 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
.availableWidth = width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right(),
.palette = &st::historyComposeAreaPalette,
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = crl::now(),
.paused = p.inactive(),
.now = now,
.paused = paused,
.elisionLines = 1,
});
} else {

View File

@@ -70,6 +70,7 @@ class RequestsBar;
struct PreparedList;
class SendFilesWay;
class SendAsButton;
class SpoilerAnimation;
enum class ReportReason;
class ChooseThemeController;
class ContinuousScroll;
@@ -102,8 +103,6 @@ class TTLButton;
class BotKeyboard;
class HistoryInner;
extern const char kOptionAutoScrollInactiveChat[];
class HistoryWidget final
: public Window::AbstractSectionWidget
, private HistoryView::CornerButtonsDelegate {
@@ -236,6 +235,7 @@ public:
[[nodiscard]] rpl::producer<> cancelRequests() const {
return _cancelRequests.events();
}
void searchInChatEmbedded(std::optional<QString> query = {});
void updateNotifyControls();
@@ -573,6 +573,7 @@ private:
// when scroll position or scroll area size changed this method
// updates the boundings of the visible area in HistoryInner
[[nodiscard]] bool hasSavedScroll() const;
void visibleAreaUpdated();
int countInitialScrollTop();
int countAutomaticScrollTop();
@@ -626,6 +627,7 @@ private:
HistoryItem *_replyEditMsg = nullptr;
Ui::Text::String _replyEditMsgText;
std::unique_ptr<Ui::SpoilerAnimation> _replySpoiler;
mutable base::Timer _updateEditTimeLeftDisplay;
object_ptr<Ui::IconButton> _fieldBarCancel;

View File

@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_forum_topic.h"
#include "data/data_peer_values.h"
#include "data/stickers/data_stickers.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_web_page.h"
@@ -987,12 +988,12 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
updateControlsGeometry(_wrap->size());
updateControlsVisibility();
updateFieldPlaceholder();
updateSendAsButton();
updateAttachBotsMenu();
//if (!_history) {
// return;
//}
const auto peer = _history->peer;
initSendAsButton(peer);
if (peer->isChat() && peer->asChat()->noParticipantInfo()) {
session().api().requestFullPeer(peer);
} else if (const auto channel = peer->asMegagroup()) {
@@ -1342,7 +1343,6 @@ void ComposeControls::init() {
initField();
initTabbedSelector();
initSendButton();
initSendAsButton();
initWriteRestriction();
initVoiceRecordBar();
initKeyHandler();
@@ -2058,17 +2058,24 @@ void ComposeControls::initSendButton() {
SendMenu::DefaultScheduleCallback(_wrap.get(), sendMenuType(), send));
}
void ComposeControls::initSendAsButton() {
session().sendAsPeers().updated(
) | rpl::filter([=](not_null<PeerData*> peer) {
return _history && (peer == _history->peer);
}) | rpl::start_with_next([=] {
void ComposeControls::initSendAsButton(not_null<PeerData*> peer) {
using namespace rpl::mappers;
// SendAsPeers::shouldChoose checks PeerData::canWrite(false).
rpl::combine(
rpl::single(peer) | rpl::then(
session().sendAsPeers().updated() | rpl::filter(_1 == peer)
),
Data::CanWriteValue(peer, false)
) | rpl::skip(1) | rpl::start_with_next([=] {
if (updateSendAsButton()) {
updateControlsVisibility();
updateControlsGeometry(_wrap->size());
orderControls();
}
}, _wrap->lifetime());
updateSendAsButton();
}
void ComposeControls::cancelInlineBot() {

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