Compare commits

...

192 Commits

Author SHA1 Message Date
John Preston
b198c9b975 Version 1.3.0.
- Improved censorship circumvention.
- Improved stability when working through proxy servers.
- Save several proxy servers to quickly switch between them.
- Use proxy for calls.
- Emoji and text replacement now happens immediately after typing
(instead of after sending) and can be rolled back using Backspace
or CTRL/CMD + Z. Replacement no longer happens when pasting text.

Added formatting shortcuts. Select text and use:
- CTRL/CMD + B/I for bold and italic
- CTRL/CMD + K to create or edit a custom link
- CTRL/CMD + SHIFT + M for monospace font
- CTRL/CMD + SHIFT + N to clear formatting
2018-06-01 01:02:13 +03:00
John Preston
cf7a779689 Fix mixed markup sending. 2018-06-01 01:01:50 +03:00
John Preston
53b3b24867 Fix build with disabled autoupdate. 2018-06-01 01:01:50 +03:00
John Preston
8edf4c8711 Alpha version 1.2.25: Fix build for Xcode. 2018-05-31 23:42:48 +03:00
John Preston
ad12d6cc46 Alpha version 1.2.25.
- Apply markdown formatting (```, `, **, __)
only when sending the message.
- Display connection quality bars in calls.
- Telegram Desktop can update itself through MTProto.
- Bug fixes and other minor improvements.
2018-05-31 23:39:12 +03:00
John Preston
7db7c05da8 Add Kosovo as a country with a phone code. 2018-05-31 21:31:44 +03:00
John Preston
43d19920e0 Apply markdown only when sending the message. 2018-05-31 21:30:28 +03:00
John Preston
bfc748cd31 Pass modifiers inside InputField submitted signal.
Also use non-MOC connections for all InputFields.
Also use Ctrl/Cmd + Enter to submit fast share box.
Fixes #4769.
2018-05-31 15:20:28 +03:00
John Preston
b3059248d4 Display colored reply preview.
Fixes #4768.
2018-05-31 14:13:11 +03:00
John Preston
0cbad9098e Active window on drop to InputField. 2018-05-31 13:35:14 +03:00
John Preston
5c5438c12e Add terms of service to the Intro. 2018-05-31 13:22:58 +03:00
John Preston
734c410879 New notifications API, defaults for users/chats. 2018-05-30 18:44:46 +03:00
John Preston
4b4e22d59d Display bot privacy policy command. 2018-05-30 17:34:21 +03:00
John Preston
8f63fa71c9 Cache updater username resolve results. 2018-05-30 17:34:21 +03:00
John Preston
267293d21b Closed beta 1.2.24.1. 2018-05-30 01:40:39 +03:00
John Preston
20ee1fa0d3 Log error code for macOS launc-after-update problem. 2018-05-30 01:39:50 +03:00
John Preston
c70e9b529a Offer to delete on editing to empty message. 2018-05-30 01:18:33 +03:00
John Preston
e42e973ed5 Show fast share button in personal chats. 2018-05-30 01:18:10 +03:00
John Preston
03037121aa Add support for autoupdate through MTProto. 2018-05-30 01:00:36 +03:00
John Preston
d257b2ee17 Allow to customize check/load way in autoupdate. 2018-05-30 00:59:03 +03:00
John Preston
59a1e13955 Use each AbstractConnection only for one time. 2018-05-28 22:43:14 +03:00
John Preston
4e858ba839 Improve connections debug logs. 2018-05-28 11:36:12 +03:00
John Preston
0eb3d20250 Use https in default auto-update prefix.
Fixes #4674.
2018-05-27 14:26:45 +03:00
John Preston
c0896f5357 Update libtgvoip to fix a possible crash. 2018-05-27 13:58:14 +03:00
John Preston
f334e2d0f4 Clear formatting at the start of the field. 2018-05-27 13:48:30 +03:00
John Preston
dfbe11efdb Fix editing of markup with links. 2018-05-27 13:48:04 +03:00
John Preston
becbad32c9 Hide stickers by emoji when editing message. 2018-05-27 13:46:47 +03:00
John Preston
46976c4e03 Display signal bars in calls. 2018-05-27 12:35:38 +03:00
John Preston
1e4cf4c466 Support correct Ukrainian plural rules. 2018-05-26 19:52:39 +03:00
John Preston
2e1517474d Alpha version 1.2.24: Fix link preview. 2018-05-26 19:02:36 +03:00
John Preston
08897aa83a Alpha version 1.2.24.
- Add links with custom text from context menu or by Ctrl/Cmd + K.
2018-05-26 18:48:11 +03:00
John Preston
7252e9b266 Apply max length limit to initial text. 2018-05-26 18:39:13 +03:00
John Preston
07d8dafa5e Allow inserting links with custom text.
Fixes #4737.
2018-05-26 18:07:51 +03:00
John Preston
cca46448fe Alpha version 1.2.23.
- Apply formatting from input field context menu.
- Apply formatting by hotkeys.
- Bug fixes and other minor improvements.
2018-05-25 23:39:45 +03:00
John Preston
3f7947b404 Use input method commit events to commit replacements.
Partially fixes #4727.
2018-05-25 23:36:32 +03:00
John Preston
4870558827 Apply formatting from context menu or shortcuts. 2018-05-25 23:31:18 +03:00
John Preston
c23ec41622 Don't replace 'code' tag inside a possible 'pre'.
Partially fixes #4728.
2018-05-25 21:02:44 +03:00
John Preston
544aef19b4 Revert markdown by backspace only temporarily.
Don't revert if you move cursor by arrows or Home/End.
2018-05-25 21:02:38 +03:00
John Preston
24834ced9e Fix input field palette setup.
Fixes #4732. Fixes #4734.
2018-05-25 20:50:36 +03:00
John Preston
10fa6f0c13 Ignore 'Replace emoji' setting in markdown.
Partially fixes #4727. Fixes #4731.
2018-05-25 20:50:26 +03:00
John Preston
7a32ad5409 Improve proxy phrases. 2018-05-25 20:46:15 +03:00
John Preston
4f7df6987c Alpha version 1.2.22.
- Use markdown in media captions
(**bold**, __italic__, `tag` and ```code```).
- Use emoji replacement in media captions,
group and channel titles and descriptions (:like: etc.)
- Markdown replacement now happens immediately
after typing (instead of after sending) and can be
rolled back using Backspace or Ctrl/Cmd + Z.
Replacement no longer happens when pasting text.
2018-05-24 20:25:05 +03:00
John Preston
3d75d21a3e Fix build in Linux 32bit. 2018-05-24 20:25:04 +03:00
John Preston
250718e766 Fix build for Xcode. 2018-05-24 20:25:04 +03:00
John Preston
a5cbade8ec Update libtgvoip. 2018-05-24 20:25:04 +03:00
John Preston
3ac50cf77f Move message text to send media box caption field. 2018-05-24 17:57:41 +03:00
John Preston
f35bf41d26 Enable instant replaces in title edit fields. 2018-05-24 17:57:35 +03:00
John Preston
f1816815a9 Refresh last post on proxy promotion refresh. 2018-05-24 17:12:57 +03:00
John Preston
37bf9ffcff Show bad proxy configuration error. 2018-05-24 16:40:19 +03:00
John Preston
5e7642b42a Support markdown and replaces in media captions. 2018-05-24 16:03:21 +03:00
John Preston
6f6ec217e3 Support markdown replaces in Ui::InputField. 2018-05-24 15:31:48 +03:00
John Preston
017ec87d60 Replace FlatTextarea with InputField. 2018-05-22 13:45:22 +03:00
John Preston
30dd8fe070 Unite InputField and InputArea.
Also support and use instant replaces in InputField-s.
2018-05-20 23:37:59 +03:00
John Preston
8e442563f2 Enable checked auth key creation. 2018-05-20 23:20:48 +03:00
John Preston
c43dcf0567 Alpha version 1.2.21: Add some setup langs. 2018-05-18 21:57:06 +03:00
John Preston
f2a5a29d12 Alpha version 1.2.21: Fix build for Xcode. 2018-05-18 21:07:31 +03:00
John Preston
678b9a8eb5 Alpha version 1.2.21.
- Support domain names in mtproto proxy.
- Bug fixes and other minor improvements.
2018-05-18 20:36:21 +03:00
John Preston
96f7c0c02e Don't try to logout CDN dcs. 2018-05-18 19:41:59 +03:00
John Preston
66b7b6da2a Replace xD by emoji only after space. 2018-05-18 18:25:05 +03:00
John Preston
38daffdbfe Handle the ADMINS_TOO_MUCH error for channels. 2018-05-18 17:16:14 +03:00
John Preston
0238c03956 Return emoji replace by ":o" after a space.
Fixes #4700.
2018-05-18 17:15:02 +03:00
John Preston
425e56b3ea Apply proxy settings in mtproto-key-destroyer. 2018-05-18 15:20:28 +03:00
John Preston
4478c0a143 Resolve domain names for proxy servers.
Also use dc_id-checked auth key creation.

Fixes #4695.
2018-05-18 10:57:11 +03:00
John Preston
a053384618 Fix mtproto-proxy working with domain names.
Also refactor a bit TcpConnection and HttpConnection classes.
2018-05-17 13:27:49 +03:00
John Preston
694e8cd19f Remove MSVC compiler bug workaround.
It works fine in Visual Studio 2017 15.7.1.
2018-05-17 11:15:54 +03:00
John Preston
4bcd1e3c59 Allow editing last sent saved message by up key.
Also update libtgvoip.
Also replace 🤷 with a shrug.
2018-05-15 21:13:37 +03:00
John Preston
5f063c0151 Display information about proxy sponsor. 2018-05-15 20:22:28 +03:00
John Preston
5a1d4d55c6 Display a disclaimer about proxy sponsor. 2018-05-15 19:38:27 +03:00
John Preston
d3f85b4c4e Display channels promoted by proxy on top. 2018-05-13 21:26:35 +03:00
John Preston
df9ec4b466 Alpha version 1.2.20.
- Emoji and text replacements are done while you type the message.
- Revert emoji and text replacements by pressing backspace.
- Disable emoji replacements or suggestions in Settings.
- Some critical bug fixes.
2018-05-13 21:25:31 +03:00
John Preston
4f9507ed97 Fix build for Xcode 9.3.1 and OS X 10.6-10.7. 2018-05-13 21:12:44 +03:00
John Preston
f761b6aa9e Backport critical bugfix from '4f959b6b30' commit to Qt patch. 2018-05-13 20:35:40 +03:00
John Preston
168a7ce2e5 Add "Suggest emoji replacements" checkbox.
Also emoji suggestions insert an instant emoji replacement.
2018-05-13 18:56:08 +03:00
John Preston
4b763a76df Instant in-field emoji and text replaces.
Fixes #4410. Fixes #522.
2018-05-13 18:14:02 +03:00
John Preston
8764da787b Don't logout on some server-side problems. 2018-05-13 12:19:34 +03:00
John Preston
7d8ba15252 Allow to report messages in supergroups. 2018-05-10 17:15:16 +03:00
John Preston
96c0c30f7c Fix possible crash. 2018-05-10 17:15:04 +03:00
John Preston
bb6ab5314c Fix recent stickers saving and possible crash.
Regression was introduced in 97c15865a5.
2018-05-10 15:03:02 +03:00
John Preston
e3c6abfc3d Fix possible crash in reply returns. 2018-05-10 14:56:36 +03:00
John Preston
5c5bccae0b Fix proxy icon on retina and on theme changes.
Fixes #4668.
2018-05-10 13:34:06 +03:00
John Preston
296e009808 Fix proxy icon doubling.
Fixes #4666.
2018-05-10 13:24:52 +03:00
John Preston
4d84781a65 Display connecting state in history top bar. 2018-05-10 13:16:21 +03:00
John Preston
710b9bf454 Fix build for MSVC 15.7.
Fixes #4661. Fixes #4667.
2018-05-10 11:13:13 +03:00
John Preston
0f54315495 Alpha version 1.2.19.
- Enable proxy for calls in Settings.
- Bug fixes and other minor improvements.
2018-05-08 21:09:45 +03:00
John Preston
c3fc91a6fc Fix couple of possible crashes. 2018-05-08 21:06:56 +03:00
John Preston
d2048f3c25 New connecting status design. 2018-05-08 20:31:33 +03:00
John Preston
cc2c13d018 Fix build with TDESKTOP_DISABLE_AUTOUPDATE.
Fixes #4655.
2018-05-07 00:34:58 +03:00
John Preston
48c1576d7f Add 'Use proxy for calls' option. 2018-05-07 00:29:53 +03:00
John Preston
d2fa8ef0b0 Alpha version 1.2.18: Fix OS X old build. 2018-05-05 22:30:42 +03:00
John Preston
97b576f446 Alpha version 1.2.18.
- Improve working through proxy servers.
- Bug fixes and other minor improvements.
2018-05-05 22:07:05 +03:00
John Preston
257dfa6b3f Improve phone rules checking. 2018-05-05 21:55:39 +03:00
John Preston
678d2a58c5 Improve proxy row design. 2018-05-05 21:55:39 +03:00
John Preston
e0431d270b Allow cdn requests through mtproto proxy. 2018-05-05 21:55:39 +03:00
John Preston
7797e5a3b7 Improve proxies box design. 2018-05-05 21:55:39 +03:00
John Preston
d15b0cdb08 Improve infinite radial animation. 2018-05-05 21:55:39 +03:00
John Preston
1af2769209 Closed beta 1.2.17.4: Fix option lookup. 2018-05-05 21:55:39 +03:00
John Preston
e6906b84f3 Closed beta 1.2.17.3: Fix layout. 2018-05-05 21:55:39 +03:00
John Preston
ca0f6c7ded Closed beta 1.2.17.3. 2018-05-05 21:55:39 +03:00
John Preston
f9ff676e57 Improve proxies box design and progress animation. 2018-05-05 21:55:38 +03:00
John Preston
db7041f2dc Send different dns requests for simple config. 2018-05-05 21:55:38 +03:00
John Preston
ad1f089802 Read autoupdate prefix from config. 2018-05-05 21:55:38 +03:00
John Preston
62c812858e Improve config re-requesting. 2018-05-05 21:55:38 +03:00
John Preston
4bf66cb6e9 Better special config implementation. 2018-05-05 21:55:38 +03:00
John Preston
95fee543ec Try all available endpoints from config. 2018-05-05 21:55:38 +03:00
John Preston
df4daca15b Display connecting/online proxy state. 2018-05-05 21:55:38 +03:00
John Preston
f794d8dbd8 Check proxy availability in ProxiesBox. 2018-05-05 21:55:38 +03:00
John Preston
9935a36c3d Create and edit proxy box. 2018-05-05 21:55:38 +03:00
John Preston
a7c77682d7 Apply, delete and restore proxies in the box. 2018-05-05 21:55:38 +03:00
John Preston
8bbea976ea Display proxies list in a box. 2018-05-05 21:55:38 +03:00
John Preston
900d1ddb36 Support multiple proxies in local storage. 2018-05-05 21:55:38 +03:00
John Preston
8e99135f37 Support tg://proxy links. 2018-05-05 21:55:37 +03:00
John Preston
dc9483e07a Fix possible deadlock.
Some unknown code (like getSession) was called while holding
_requestsByDcLock mutex which could lead to a deadlock.

Now all access points to _requestsByDc are simple.
2018-05-05 21:55:37 +03:00
John Preston
48e913bf2c Use static version map for autoupdates. 2018-05-05 21:55:37 +03:00
John Preston
993cb987a6 Improve autoupdate code, move it from Application. 2018-05-05 21:55:37 +03:00
John Preston
65f968ec1b Update API scheme to layer 78. 2018-05-05 21:55:37 +03:00
John Preston
93f6d4b6e7 Support many config endpoints for one dc+params. 2018-05-05 21:55:37 +03:00
John Preston
7482025c10 Support work with different dcs on a single IP. 2018-05-05 21:55:37 +03:00
John Preston
909acb25fd Requesting config if can't connect. 2018-05-05 21:55:37 +03:00
John Preston
4a9db99082 Use application level proxy settings. 2018-05-05 21:55:37 +03:00
John Zimmermann
a2606c4fc4 fix libressl-2.7 compat (#4633) 2018-04-28 21:57:02 +03:00
Max Razumov
647c609bf8 Update supported version of Ubuntu (#4612) 2018-04-28 21:55:15 +03:00
John Preston
cf98025177 Use exact tagged version of OpenAL.
I hope if fixes #4531.
2018-04-18 22:10:21 +04:00
John Preston
e0f20e9f82 Version 1.2.17: Add changelog entry. 2018-04-08 21:34:33 +04:00
John Preston
597a5c9d75 Version 1.2.17: Fix undefined behaviour on exit.
Core::Launcher didn't have virtual destructor it required.
2018-04-08 21:32:48 +04:00
John Preston
d055908f4f Version 1.2.16: Improve release build scripts. 2018-04-08 18:23:12 +04:00
John Preston
f3eac6b259 Version 1.2.16: Fix text processing crash in OS X 10.6. 2018-04-08 18:21:40 +04:00
John Preston
87d6081408 Version 1.2.16: Fix build in Xcode 9.3. 2018-04-07 12:47:08 +04:00
John Preston
dd53bd1c55 Version 1.2.16.
- Bug fixes and other minor improvements.
2018-04-07 12:20:46 +04:00
Kirsan
3ff033cdf3 Fix for https://github.com/telegramdesktop/tdesktop/issues/4544
(cherry picked from commit d99c757d44d7c31fbb0eb290f273ad4d2d464255)
2018-04-07 12:13:52 +04:00
John Preston
c1c3b6a7e5 Disable emoji suggestions for :-D/:-P 2018-04-07 11:51:17 +04:00
John Preston
64d5a6acd5 Fix game scores display. 2018-04-07 11:41:48 +04:00
John Preston
e5b2e0a6b5 Fix memory clearing from photos/documents. 2018-04-07 11:01:32 +04:00
John Preston
9895b45293 Fix calls supported layer arguments. 2018-04-07 11:00:43 +04:00
John Preston
811fc43b63 Fix Qt text processing crash.
Fixes #4551.
2018-03-30 18:18:35 +04:00
John Preston
9dcfa3ad6e Version 1.2.15.
- Bug fixes and other minor improvements.
2018-03-26 02:02:58 +04:00
John Preston
67bda19458 Fix inline bot messages with previews. 2018-03-26 02:01:13 +04:00
John Preston
6c38919c3d Scroll history to down when sharing contact. 2018-03-26 01:47:05 +04:00
John Preston
ce9445287c Extend huge local cache map crash annotations. 2018-03-26 00:56:41 +04:00
John Preston
d4bd8862bd Fix switching between stickers pan and panel.
Fixes #4537.
2018-03-26 00:54:02 +04:00
John Preston
6904e023d3 Increment dropdown emoji selection in recent list.
Fixes #4539.
2018-03-26 00:28:23 +04:00
John Preston
91a7a77bb0 Fix case-insensitive emoji suggestions.
Fixes #4528, fixes #4511, fixes #4535.
2018-03-26 00:18:14 +04:00
John Preston
d9306e3e30 Fix parent link refresh for GIF webpage preview.
Fixes #4534.
2018-03-26 00:18:14 +04:00
John Preston
efdd3df129 Fix working with webpage preview GIFs.
Fixes #4533.
2018-03-26 00:18:14 +04:00
John Preston
63098d3c7d Work better with 'unlimited' config time limits. 2018-03-26 00:18:14 +04:00
Vasilii Babich
aa5781b550 Update some details in Xcode build instructions. 2018-03-25 19:29:38 +04:00
John Preston
d6e1862c08 Fix crash in AdminLog view with deleted delegates. 2018-03-22 02:27:14 +04:00
John Preston
9aa2831fef Version 1.2.14.
- Discover new stickers.
Type one emoji to see suggestions from popular sticker sets.
Suggestions from your installed sticker sets will come first.
- Search for Stickers. Click on the new search icon
to access your sticker sets or find new ones.
- Quick Reply. Double click near a message for a quick reply.
2018-03-21 18:05:45 +04:00
John Preston
70eb29c1a9 Alpha version 1.2.13.
- Bug fixes and other minor improvements.
2018-03-21 13:46:32 +04:00
John Preston
13e07b1623 Add some debug information. 2018-03-21 13:46:22 +04:00
John Preston
27ce1f8d44 Show something inside an 'empty' message. 2018-03-20 18:51:51 +04:00
John Preston
38c20fc3c2 Return one old way for requesting special config. 2018-03-20 18:21:36 +04:00
John Preston
4a32b00068 Setting for cloud stickers suggestions. 2018-03-20 01:09:03 +04:00
John Preston
3406f88fdc Single place for joining channels, in ApiWrap. 2018-03-20 01:09:03 +04:00
Marco Trevisan
c96cb37680 Travis flags update (#4506)
* travis: don't build alsoft tools, examples and tests

* travis: don't try to build with mir client support
2018-03-16 17:39:29 +03:00
John Preston
0d415837a0 Destroy old info button in top bar widget.
Fixes #4503.
2018-03-15 18:22:55 +03:00
John Preston
9dc48522d8 Fix multi-forward notification layout (long name). 2018-03-15 03:12:00 +03:00
John Preston
31b82a5d92 Show audio file performer-title in dialogs list. 2018-03-15 03:12:00 +03:00
John Preston
87ab4d9dd1 Improve local search in sticker sets.
- Don't index special sticker sets, like "Favorite stickers".
- Show "Not found." if no local results and waiting for server-side.
2018-03-15 03:11:59 +03:00
John Preston
b6e7625016 Improve case-insensitive emoji suggestions. 2018-03-15 03:11:59 +03:00
Sven-Hendrik Haase
c5e6bfce95 Fix wrong uname flag used
This is required because uname -p actually returns "unknown" for some hardware. The uname help documents this by stating that -p is non-portable. The -m flag is the one to use.
2018-03-14 15:01:26 +03:00
John Preston
7a849b2899 Fix crash on album parts being deleted. 2018-03-13 13:54:24 +03:00
John Preston
999fa39d7c Fix shared links layout for webpage previews.
Fixes #4489.
2018-03-13 13:29:56 +03:00
John Preston
7de15ce5cf Fix inline bots with photos. 2018-03-13 13:29:42 +03:00
John Preston
f792b0052f Refresh caption Text after media is being sent.
Fixes #4488.
2018-03-13 13:19:06 +03:00
John Preston
57d0b1d215 Show error when joining a full group. 2018-03-12 10:55:30 +03:00
John Preston
7691654cb8 Alpha version 1.2.12.
- Bug fixes and other minor improvements.
2018-03-12 00:00:08 +03:00
John Preston
c76e4b6b3c Fix: cancel empty reply by Escape. 2018-03-11 23:58:14 +03:00
John Preston
bda39cc6f6 Cancel empty reply by Escape. 2018-03-11 23:48:18 +03:00
John Preston
060cdfea86 Cancel reply when setting a forwarding draft. 2018-03-11 23:42:03 +03:00
John Preston
b1cc7b25ba Add group/channel info limit in EditPeerInfoBox. 2018-03-11 23:22:43 +03:00
John Preston
1e0fe70dc3 Focus correct widget in intro steps.
The old way some random shown widget could've been focused.
2018-03-11 22:55:28 +03:00
John Preston
8ed167c5fa Change domain fronting url and host header. 2018-03-11 22:55:28 +03:00
John Preston
dabf8414be Remove old debug information. 2018-03-11 22:21:17 +03:00
John Preston
a0eb64428e Use new config fields for revoke settings. 2018-03-11 22:21:17 +03:00
Patrick Eigensatz
dd1beb1d91 Let emoji suggestions be case insensitive
Closes #3985
2018-03-11 22:01:18 +03:00
John Preston
bb35d71fdc Attempt to fix access to a deleted item view. 2018-03-10 15:47:19 +03:00
John Preston
42a7e86e51 Alpha version 1.2.11.
- Bug fixes and other minor improvements.
2018-03-10 00:56:36 +03:00
John Preston
2f3540dadc Fix sending an album after cancel of one media. 2018-03-10 00:55:59 +03:00
John Preston
eb00641dfa Mark autoplayed voice/video messages as read. 2018-03-10 00:55:58 +03:00
John Preston
bfe7bf2c11 Paste image to SendFilesBox even if it has an url.
Fixes #4483.
2018-03-10 00:55:58 +03:00
Marco Trevisan (Treviño)
e88c575d4a linux: use $HOME to determine actual user path
As it could be different in confined environments.
2018-03-10 00:55:24 +03:00
Marco Trevisan (Treviño)
0de9c62675 linux-desktop-environment: detect Ubuntu properly enabling features
In Ubuntu (running in GNOME) we support AppIndicator and
Unity counters still.
2018-03-10 00:52:06 +03:00
Sean
9dc3847dbe Fix Typo 2018-03-10 00:50:05 +03:00
Marco Trevisan
9dc03c4f0f Linux ARM compile fixes (#4399)
This fixes errors when compiling in ARM
2018-03-09 23:48:47 +03:00
John Preston
def21367a3 Allow to reply by double click on the timestamp. 2018-03-09 21:22:31 +03:00
John Preston
33fe1b6389 Show no results message in share box search. 2018-03-09 21:22:31 +03:00
John Preston
76cb5677b2 Fix webpage edit display in channel admin log. 2018-03-09 21:22:31 +03:00
John Preston
8c3b7f6417 Ignore second tray icon click in short time.
Fixes #4479.
2018-03-09 21:22:31 +03:00
John Preston
e6c0f0f774 Up arrow always edits last available message.
Fixes #4480.
2018-03-09 21:22:31 +03:00
John Preston
6bd5301828 Fix crash when accessing a deleted item view. 2018-03-09 21:22:31 +03:00
John
73c0c4507a fix build against libressl 2018-03-09 21:12:34 +03:00
John Preston
506b0806d6 Fix build instructions for Linux. 2018-03-09 01:55:18 +03:00
326 changed files with 16657 additions and 8891 deletions

View File

@@ -486,6 +486,9 @@ buildOpenAL() {
-D CMAKE_INSTALL_PREFIX=$OPENAL_PATH \
-D CMAKE_BUILD_TYPE=Release \
-D LIBTYPE=STATIC \
-D ALSOFT_EXAMPLES=OFF \
-D ALSOFT_TESTS=OFF \
-D ALSOFT_UTILS=OFF \
..
make $MAKE_ARGS
sudo make install
@@ -582,10 +585,10 @@ buildCustomQt() {
info_msg "Downloading and building patched qt"
if [ -d "$EXTERNAL/qt${QT_VERSION}" ]; then
rm -rf "$EXTERNAL/qt${QT_VERSION}"
sudo rm -rf "$EXTERNAL/qt${QT_VERSION}"
fi
cd $QT_PATH
rm -rf *
sudo rm -rf *
cd "$EXTERNAL"
git clone git://code.qt.io/qt/qt5.git qt${QT_VERSION}
@@ -608,7 +611,7 @@ buildCustomQt() {
./configure -prefix $QT_PATH -release -opensource -confirm-license -qt-zlib \
-qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz -qt-pcre -qt-xcb \
-qt-xkbcommon-x11 -no-opengl -no-gtkstyle -static \
-nomake examples -nomake tests \
-nomake examples -nomake tests -no-mirclient \
-dbus-runtime -no-gstreamer -no-mtdev # <- Not sure about these
make $MAKE_ARGS
sudo make install

View File

@@ -15,7 +15,7 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
* Windows XP - Windows 10 (**not** RT)
* Mac OS X 10.8 - Mac OS X 10.11
* Mac OS X 10.6 - Mac OS X 10.7 (separate build)
* Ubuntu 12.04 - Ubuntu 16.04
* Ubuntu 12.04 - Ubuntu 18.04
* Fedora 22 - Fedora 24
## Third-party

View File

@@ -1,5 +1,5 @@
diff --git a/configure b/configure
index cb8d78f..cadb3f0 100755
index cb8d78fd3cb..cadb3f0a880 100755
--- a/configure
+++ b/configure
@@ -511,7 +511,8 @@ if [ "$BUILD_ON_MAC" = "yes" ]; then
@@ -13,7 +13,7 @@ index cb8d78f..cadb3f0 100755
echo " Xcode not set up properly. You may need to confirm the license" >&2
echo " agreement by running /usr/bin/xcodebuild without arguments." >&2
diff --git a/mkspecs/common/g++-macx.conf b/mkspecs/common/g++-macx.conf
index 086510d..c485967 100644
index 086510dd963..c485967863d 100644
--- a/mkspecs/common/g++-macx.conf
+++ b/mkspecs/common/g++-macx.conf
@@ -14,7 +14,13 @@ QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -gdwarf-2
@@ -32,7 +32,7 @@ index 086510d..c485967 100644
QMAKE_XCODE_GCC_VERSION = com.apple.compilers.llvmgcc42
diff --git a/mkspecs/features/mac/default_pre.prf b/mkspecs/features/mac/default_pre.prf
index 0cc8cd6..ca9725b 100644
index 0cc8cd6dfdd..ca9725b7791 100644
--- a/mkspecs/features/mac/default_pre.prf
+++ b/mkspecs/features/mac/default_pre.prf
@@ -12,7 +12,9 @@ isEmpty(QMAKE_XCODE_DEVELOPER_PATH) {
@@ -47,7 +47,7 @@ index 0cc8cd6..ca9725b 100644
}
diff --git a/src/gui/image/qbmphandler.cpp b/src/gui/image/qbmphandler.cpp
index bb79a13..5d595bc 100644
index bb79a139b3c..5d595bc3b34 100644
--- a/src/gui/image/qbmphandler.cpp
+++ b/src/gui/image/qbmphandler.cpp
@@ -220,6 +220,10 @@ static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, int offset, int
@@ -74,7 +74,7 @@ index bb79a13..5d595bc 100644
if (ncols > 0) { // read color table
uchar rgb[4];
diff --git a/src/gui/painting/qpaintengine_p.h b/src/gui/painting/qpaintengine_p.h
index ebff950..4300ca4 100644
index ebff9509ab2..4300ca4c0f0 100644
--- a/src/gui/painting/qpaintengine_p.h
+++ b/src/gui/painting/qpaintengine_p.h
@@ -87,8 +87,18 @@ public:
@@ -98,7 +98,7 @@ index ebff950..4300ca4 100644
// Make sure we're inside the viewport.
diff --git a/src/gui/text/qtextlayout.cpp b/src/gui/text/qtextlayout.cpp
index 4879ae5..56cdcba 100644
index 4879ae51d7d..56cdcbaf01c 100644
--- a/src/gui/text/qtextlayout.cpp
+++ b/src/gui/text/qtextlayout.cpp
@@ -654,6 +654,9 @@ int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const
@@ -175,7 +175,7 @@ index 4879ae5..56cdcba 100644
inline void resetRightBearing()
diff --git a/src/gui/text/qtextlayout.h b/src/gui/text/qtextlayout.h
index cbe42c3..b273db7 100644
index cbe42c38444..b273db7e78c 100644
--- a/src/gui/text/qtextlayout.h
+++ b/src/gui/text/qtextlayout.h
@@ -194,6 +194,9 @@ private:
@@ -188,8 +188,21 @@ index cbe42c3..b273db7 100644
};
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index 360f9722c70..f28f289ef6a 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -118,6 +118,8 @@ QHttpNetworkConnectionPrivate::~QHttpNetworkConnectionPrivate()
{
for (int i = 0; i < channelCount; ++i) {
if (channels[i].socket) {
+ // Patch: backport critical bugfix from '4f959b6b30' commit.
+ QObject::disconnect(channels[i].socket, Q_NULLPTR, &channels[i], Q_NULLPTR);
channels[i].socket->close();
delete channels[i].socket;
}
diff --git a/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm b/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm
index ca7afb7..25ae500 100644
index ca7afb7d1b9..25ae50008db 100644
--- a/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm
+++ b/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm
@@ -256,6 +256,13 @@ static void getFontDescription(CTFontDescriptorRef font, FontDescription *fd)
@@ -206,8 +219,22 @@ index ca7afb7..25ae500 100644
fd->styleName = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute);
fd->weight = QFont::Normal;
fd->style = QFont::StyleNormal;
diff --git a/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm b/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
index 6e2c8a2a9af..3cace8abcbc 100644
--- a/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
+++ b/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
@@ -717,7 +717,8 @@ void QCoreTextFontEngine::getUnscaledGlyph(glyph_t, QPainterPath *, glyph_metric
QFixed QCoreTextFontEngine::emSquareSize() const
{
- return QFixed::QFixed(int(CTFontGetUnitsPerEm(ctfont)));
+ // Patch: Fix build for Xcode 9.3.1.
+ return QFixed(int(CTFontGetUnitsPerEm(ctfont)));
}
QFontEngine *QCoreTextFontEngine::cloneWithSize(qreal pixelSize) const
diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
index 92358ec..694fee7 100644
index 92358ecc745..694fee73507 100644
--- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
+++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
@@ -213,7 +213,8 @@ static void cleanupCocoaApplicationDelegate()
@@ -244,7 +271,7 @@ index 92358ec..694fee7 100644
- (void)appleEventQuit:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
diff --git a/src/plugins/platforms/cocoa/qcocoacursor.mm b/src/plugins/platforms/cocoa/qcocoacursor.mm
index b81b9a0..4e59e83 100644
index b81b9a0b1c2..4e59e833b1d 100644
--- a/src/plugins/platforms/cocoa/qcocoacursor.mm
+++ b/src/plugins/platforms/cocoa/qcocoacursor.mm
@@ -81,7 +81,7 @@ void QCocoaCursor::setPos(const QPoint &position)
@@ -256,8 +283,41 @@ index b81b9a0..4e59e83 100644
CGEventPost(kCGHIDEventTap, e);
CFRelease(e);
}
diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm
index 9850f83dea8..b2e1d3dfda7 100644
--- a/src/plugins/platforms/cocoa/qcocoahelpers.mm
+++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm
@@ -649,9 +649,10 @@ OSStatus qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGIm
// Verbatim copy if HIViewDrawCGImage (as shown on Carbon-Dev)
OSStatus err = noErr;
- require_action(inContext != NULL, InvalidContext, err = paramErr);
- require_action(inBounds != NULL, InvalidBounds, err = paramErr);
- require_action(inImage != NULL, InvalidImage, err = paramErr);
+ // Patch: Fix build on latest Xcode.
+ //require_action(inContext != NULL, InvalidContext, err = paramErr);
+ //require_action(inBounds != NULL, InvalidBounds, err = paramErr);
+ //require_action(inImage != NULL, InvalidImage, err = paramErr);
CGContextSaveGState( inContext );
CGContextTranslateCTM (inContext, 0, inBounds->origin.y + CGRectGetMaxY(*inBounds));
@@ -660,9 +661,11 @@ OSStatus qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGIm
CGContextDrawImage(inContext, *inBounds, inImage);
CGContextRestoreGState(inContext);
-InvalidImage:
-InvalidBounds:
-InvalidContext:
+
+// Patch: Fix build on latest Xcode.
+//InvalidImage:
+//InvalidBounds:
+//InvalidContext:
return err;
}
diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm
index 9fd05a6..dea6072 100644
index 9fd05a65ee9..dea60720e78 100644
--- a/src/plugins/platforms/cocoa/qcocoaintegration.mm
+++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm
@@ -402,14 +402,24 @@ void QCocoaIntegration::updateScreens()
@@ -288,7 +348,7 @@ index 9fd05a6..dea6072 100644
QCocoaScreen *QCocoaIntegration::screenAtIndex(int index)
diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.mm b/src/plugins/platforms/cocoa/qcocoakeymapper.mm
index e46eaff..c62db53 100644
index e46eaff6be3..c62db534a2d 100644
--- a/src/plugins/platforms/cocoa/qcocoakeymapper.mm
+++ b/src/plugins/platforms/cocoa/qcocoakeymapper.mm
@@ -382,6 +382,12 @@ bool QCocoaKeyMapper::updateKeyboard()
@@ -315,7 +375,7 @@ index e46eaff..c62db53 100644
}
return ret;
diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
index 83c960d..03ae969 100755
index 83c960d9317..03ae9696afe 100755
--- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
+++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
@@ -102,7 +102,10 @@ QT_USE_NAMESPACE
@@ -483,7 +543,7 @@ index 83c960d..03ae969 100755
}
@end
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm
index 4d0458a..3357a5e 100644
index 4d0458a4aa2..3357a5ef817 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.mm
+++ b/src/plugins/platforms/cocoa/qcocoawindow.mm
@@ -167,7 +167,8 @@ static bool isMouseEvent(NSEvent *ev)
@@ -546,7 +606,7 @@ index 4d0458a..3357a5e 100644
[iconButton setImage:image];
[image release];
diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm
index a18ee7f..1f91feb 100644
index a18ee7ff71d..1f91feb0ae8 100644
--- a/src/plugins/platforms/cocoa/qnsview.mm
+++ b/src/plugins/platforms/cocoa/qnsview.mm
@@ -393,7 +393,9 @@ static NSString *_q_NSWindowDidChangeOcclusionStateNotification = nil;
@@ -615,7 +675,7 @@ index a18ee7f..1f91feb 100644
}
return [super performKeyEquivalent:nsevent];
diff --git a/src/tools/qlalr/lalr.cpp b/src/tools/qlalr/lalr.cpp
index c680764..e2a7aaf 100644
index c68076477f3..e2a7aafa586 100644
--- a/src/tools/qlalr/lalr.cpp
+++ b/src/tools/qlalr/lalr.cpp
@@ -246,11 +246,13 @@ void Grammar::buildExtendedGrammar ()
@@ -655,7 +715,7 @@ index c680764..e2a7aaf 100644
continue;
diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp
index 7396808..7178aec 100644
index 7396808442e..7178aecf800 100644
--- a/src/widgets/kernel/qwidget.cpp
+++ b/src/widgets/kernel/qwidget.cpp
@@ -4722,6 +4722,17 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset,
@@ -708,7 +768,7 @@ index 7396808..7178aec 100644
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
diff --git a/src/widgets/styles/qmacstyle_mac.mm b/src/widgets/styles/qmacstyle_mac.mm
index 0845a5e..5735cb6 100644
index 0845a5eb02f..5735cb6b396 100644
--- a/src/widgets/styles/qmacstyle_mac.mm
+++ b/src/widgets/styles/qmacstyle_mac.mm
@@ -3667,9 +3667,11 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
@@ -726,7 +786,7 @@ index 0845a5e..5735cb6 100644
}
diff --git a/src/widgets/util/qsystemtrayicon_qpa.cpp b/src/widgets/util/qsystemtrayicon_qpa.cpp
index f98aeaf..00c0734 100644
index f98aeaf6782..00c0734129e 100644
--- a/src/widgets/util/qsystemtrayicon_qpa.cpp
+++ b/src/widgets/util/qsystemtrayicon_qpa.cpp
@@ -99,13 +99,18 @@ void QSystemTrayIconPrivate::updateIcon_sys()
@@ -755,7 +815,7 @@ index f98aeaf..00c0734 100644
}
diff --git a/src/widgets/widgets/qwidgetlinecontrol.cpp b/src/widgets/widgets/qwidgetlinecontrol.cpp
index 75f3059..980f2be 100644
index 75f30599be4..980f2be1e93 100644
--- a/src/widgets/widgets/qwidgetlinecontrol.cpp
+++ b/src/widgets/widgets/qwidgetlinecontrol.cpp
@@ -1867,7 +1867,8 @@ void QWidgetLineControl::processKeyEvent(QKeyEvent* event)
@@ -769,7 +829,7 @@ index 75f3059..980f2be 100644
#ifndef QT_NO_COMPLETER
complete(event->key());
diff --git a/src/widgets/widgets/qwidgettextcontrol.cpp b/src/widgets/widgets/qwidgettextcontrol.cpp
index 96438a0..b0b7206 100644
index 96438a0bdf7..b0b72064056 100644
--- a/src/widgets/widgets/qwidgettextcontrol.cpp
+++ b/src/widgets/widgets/qwidgettextcontrol.cpp
@@ -1342,7 +1342,8 @@ void QWidgetTextControlPrivate::keyPressEvent(QKeyEvent *e)

View File

@@ -221,6 +221,19 @@ index f74d4d4229..8ad672c9fe 100644
};
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index c4cb8e65c0..45793e364f 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -110,6 +110,8 @@ QHttpNetworkConnectionPrivate::~QHttpNetworkConnectionPrivate()
{
for (int i = 0; i < channelCount; ++i) {
if (channels[i].socket) {
+ // Patch: backport critical bugfix from '4f959b6b30' commit.
+ QObject::disconnect(channels[i].socket, Q_NULLPTR, &channels[i], Q_NULLPTR);
channels[i].socket->close();
delete channels[i].socket;
}
diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp
index 41834b21ae..8cdf4ab145 100644
--- a/src/network/socket/qnativesocketengine_win.cpp
@@ -417,6 +430,20 @@ index 566abf2126..5b9c714ffa 100644
fd->styleName = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute);
fd->weight = QFont::Normal;
fd->style = QFont::StyleNormal;
diff --git a/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm b/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
index 7b459584ea..2ed2fd9b3b 100644
--- a/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
+++ b/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
@@ -764,7 +764,8 @@ void QCoreTextFontEngine::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, gl
QFixed QCoreTextFontEngine::emSquareSize() const
{
- return QFixed::QFixed(int(CTFontGetUnitsPerEm(ctfont)));
+ // Patch: Fix build for Xcode 9.3.1.
+ return QFixed(int(CTFontGetUnitsPerEm(ctfont)));
}
QFontEngine *QCoreTextFontEngine::cloneWithSize(qreal pixelSize) const
diff --git a/src/plugins/platforminputcontexts/compose/compose.pro b/src/plugins/platforminputcontexts/compose/compose.pro
index 86bdd4729b..9b9c8ded08 100644
--- a/src/plugins/platforminputcontexts/compose/compose.pro

View File

@@ -40,7 +40,7 @@ lineWidth: 1px;
defaultTextPalette: TextPalette {
linkFg: windowActiveTextFg;
monoFg: windowSubTextFg;
monoFg: msgInMonoFg;
selectBg: msgInBgSelected;
selectFg: transparent; // use painter current pen instead
selectLinkFg: historyLinkInFgSelected;
@@ -172,6 +172,21 @@ inFwdTextPaletteSelected: TextPalette(defaultTextPalette) {
outFwdTextPaletteSelected: TextPalette(defaultTextPalette) {
linkFg: msgOutServiceFgSelected;
}
inReplyTextPalette: TextPalette(inTextPalette) {
linkFg: msgInDateFg;
}
inReplyTextPaletteSelected: TextPalette(inTextPaletteSelected) {
linkFg: msgInDateFgSelected;
}
outReplyTextPalette: TextPalette(outTextPalette) {
linkFg: msgOutDateFg;
}
outReplyTextPaletteSelected: TextPalette(outTextPaletteSelected) {
linkFg: msgOutDateFgSelected;
}
imgReplyTextPalette: TextPalette(defaultTextPalette) {
linkFg: msgImgReplyBarColor;
}
inSemiboldPalette: TextPalette(inTextPalette) {
linkFg: msgInServiceFg;
selectFg: msgInServiceFgSelected;
@@ -182,6 +197,9 @@ outSemiboldPalette: TextPalette(outTextPalette) {
selectFg: msgOutServiceFgSelected;
selectLinkFg: msgOutServiceFgSelected;
}
historyComposeAreaPalette: TextPalette(defaultTextPalette) {
linkFg: historyComposeAreaFgService;
}
mediaCaptionSkip: 5px;
mediaInBubbleSkip: 5px;
@@ -215,8 +233,6 @@ emojiReplaceHeight: 56px;
emojiReplaceInnerHeight: 42px;
emojiReplacePadding: 14px;
connectingPadding: margins(5px, 5px, 5px, 5px);
dragFont: font(28px semibold);
dragSubfont: font(20px semibold);
dragColor: windowSubTextFg;

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -123,6 +123,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_error_cant_add_admin_invite" = "Sorry, you can't add this user as an admin because they are not a member of this group and you are not allowed to invite them.";
"lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the blacklist and you can't unban them.";
"lng_error_cant_ban_admin" = "Sorry, you can't ban this user because they are an admin in this group and you are not allowed to demote them.";
"lng_error_admin_limit" = "Sorry, you've reached the maximum number of admins for this group.";
"lng_error_admin_limit_channel" = "Sorry, you've reached the maximum number of admins for this channel.";
"lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?";
"lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?";
"lng_sure_add_admin_unban" = "This user is currently restricted or banned. Are you sure you want to unban and promote them?";
@@ -296,6 +298,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_section_chat_settings" = "Chat Settings";
"lng_settings_replace_emojis" = "Replace emoji";
"lng_settings_suggest_emoji" = "Suggest emoji replacements";
"lng_settings_suggest_by_emoji" = "Suggest popular stickers by emoji";
"lng_settings_view_emojis" = "View list";
"lng_settings_send_enter" = "Send by Enter";
"lng_settings_send_ctrlenter" = "Send by Ctrl+Enter";
@@ -417,8 +421,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_connection_port_ph" = "Port";
"lng_connection_user_ph" = "Username";
"lng_connection_password_ph" = "Password";
"lng_connection_proxy_secret_ph" = "Secret";
"lng_connection_save" = "Save";
"lng_proxy_settings" = "Proxy settings";
"lng_proxy_use" = "Use proxy";
"lng_proxy_use_for_calls" = "Use proxy for calls";
"lng_proxy_about" = "Proxy servers may be helpful in accessing Telegram if there is no connection in a specific region.";
"lng_proxy_add" = "Add proxy";
"lng_proxy_share" = "Share";
"lng_proxy_online" = "connected";
"lng_proxy_checking" = "checking";
"lng_proxy_connecting" = "connecting";
"lng_proxy_available" = "available, {ping} ms ping";
"lng_proxy_unavailable" = "not available";
"lng_proxy_edit" = "Edit proxy";
"lng_proxy_menu_edit" = "Edit";
"lng_proxy_menu_delete" = "Delete";
"lng_proxy_menu_restore" = "Restore";
"lng_proxy_edit_share" = "Share";
"lng_proxy_address_label" = "Socket address";
"lng_proxy_credentials_optional" = "Credentials (optional)";
"lng_proxy_credentials" = "Credentials";
"lng_proxy_description" = "Your saved proxy list will be here.";
"lng_proxy_sponsor" = "Proxy sponsor";
"lng_proxy_sponsor_about" = "This channel is shown by your proxy server.\nTo remove this channel from your chats list,\ndisable the proxy in Telegram Settings.";
"lng_proxy_sponsor_warning" = "This proxy may display a sponsored channel in your chat list. This doesn't reveal any of your Telegram traffic.";
"lng_settings_blocked_users" = "Blocked users";
"lng_settings_last_seen_privacy" = "Last seen privacy";
"lng_settings_calls_privacy" = "Phone calls privacy";
@@ -528,6 +557,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_actions_section" = "Actions";
"lng_profile_bot_settings" = "Bot Settings";
"lng_profile_bot_help" = "Bot Help";
"lng_profile_bot_privacy" = "Bot Privacy Policy";
"lng_profile_invite_link_section" = "Invite link";
"lng_profile_create_public_link" = "Create public link";
"lng_profile_edit_public_link" = "Edit public link";
@@ -667,6 +697,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_report_title" = "Report channel";
"lng_report_group_title" = "Report group";
"lng_report_bot_title" = "Report bot";
"lng_report_message_title" = "Report message";
"lng_report_reason_spam" = "Spam";
"lng_report_reason_violence" = "Violence";
"lng_report_reason_pornography" = "Pornography";
@@ -819,6 +850,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_not_accessible" = "Sorry, this channel is not accessible.";
"lng_group_not_accessible" = "Sorry, this group is not accessible.";
"lng_group_full" = "Sorry, this group is full.";
"lng_channels_too_much_public_about" = "Sorry, you have reserved too many public usernames. You can revoke the link from one of your older groups or channels.";
"lng_channels_too_much_public_revoke_confirm_group" = "Are you sure you want to revoke the link {link}?\n\nThe group «{group}» will become private.";
@@ -1081,6 +1113,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_forward_msg" = "Forward Message";
"lng_context_delete_msg" = "Delete Message";
"lng_context_select_msg" = "Select Message";
"lng_context_report_msg" = "Report Message";
"lng_context_pin_msg" = "Pin Message";
"lng_context_unpin_msg" = "Unpin Message";
"lng_context_cancel_upload" = "Cancel Upload";
@@ -1223,6 +1256,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_new_version_minor" = "— Bug fixes and other minor improvements";
"lng_menu_insert_unicode" = "Insert Unicode control character";
"lng_menu_formatting" = "Formatting";
"lng_menu_formatting_bold" = "Bold";
"lng_menu_formatting_italic" = "Italic";
"lng_menu_formatting_monospace" = "Monospace";
"lng_menu_formatting_link_create" = "Create link";
"lng_menu_formatting_link_edit" = "Edit link";
"lng_menu_formatting_clear" = "Plain text";
"lng_formatting_link_create_title" = "Create link";
"lng_formatting_link_edit_title" = "Create link";
"lng_formatting_link_text" = "Text";
"lng_formatting_link_url" = "URL";
"lng_formatting_link_create" = "Create";
"lng_full_name" = "{first_name} {last_name}";
@@ -1448,6 +1493,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_info_feed_is_default" = "Group new channels";
"lng_info_feed_channels" = "Channels";
"lng_terms_signup" = "By signing up,\nyou agree to the {link}.";
"lng_terms_signup_link" = "Terms of Service";
"lng_terms_header" = "Terms of Service";
"lng_terms_agree" = "Agree & Continue";
// Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program...";

View File

@@ -98,6 +98,7 @@
387;BA;Bosnia & Herzegovina
386;SI;Slovenia
385;HR;Croatia
383;XK;Kosovo;383 XXXX XXXX;11;
382;ME;Montenegro
381;RS;Serbia;381 XX XXX XXXX;12;
380;UA;Ukraine;380 XX XXX XX XX;12;

View File

@@ -35,6 +35,9 @@
resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector<long> = ResPQ;
p_q_inner_data#83c95aec pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data;
p_q_inner_data_dc#a9f55f95 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int = P_Q_inner_data;
p_q_inner_data_temp#3c6a84d4 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 expires_in:int = P_Q_inner_data;
p_q_inner_data_temp_dc#56fddf88 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int expires_in:int = P_Q_inner_data;
server_DH_params_fail#79cb045d nonce:int128 server_nonce:int128 new_nonce_hash:int128 = Server_DH_Params;
server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:string = Server_DH_Params;
@@ -107,8 +110,13 @@ new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long =
http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait;
ipPort ipv4:int port:int = IpPort;
help.configSimple#d997c3c5 date:int expires:int dc_id:int ip_port_list:Vector<ipPort> = help.ConfigSimple;
//ipPort ipv4:int port:int = IpPort;
//help.configSimple#d997c3c5 date:int expires:int dc_id:int ip_port_list:Vector<ipPort> = help.ConfigSimple;
ipPort#d433ad73 ipv4:int port:int = IpPort;
ipPortSecret#37982646 ipv4:int port:int secret:bytes = IpPort;
accessPointRule#4679b65f phone_prefix_rules:string dc_id:int ips:vector<IpPort> = AccessPointRule;
help.configSimple#5a592a6c date:int expires:int rules:vector<AccessPointRule> = help.ConfigSimple;
---functions---
@@ -220,7 +228,7 @@ userStatusLastMonth#77ebc742 = UserStatus;
chatEmpty#9ba2d800 id:int = Chat;
chat#d91cdd54 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true admins_enabled:flags.3?true admin:flags.4?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel = Chat;
chatForbidden#7328bdb id:int title:string = Chat;
channel#450b7115 flags:# creator:flags.0?true left:flags.2?true editor:flags.3?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true democracy:flags.10?true signatures:flags.11?true min:flags.12?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChannelAdminRights banned_rights:flags.15?ChannelBannedRights participants_count:flags.17?int = Chat;
channel#c88974ac flags:# creator:flags.0?true left:flags.2?true editor:flags.3?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true democracy:flags.10?true signatures:flags.11?true min:flags.12?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChannelAdminRights banned_rights:flags.15?ChannelBannedRights participants_count:flags.17?int = Chat;
channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> = ChatFull;
@@ -296,18 +304,10 @@ auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorizat
inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer;
inputNotifyUsers#193b4417 = InputNotifyPeer;
inputNotifyChats#4a95e84e = InputNotifyPeer;
inputNotifyAll#a429b886 = InputNotifyPeer;
inputPeerNotifyEventsEmpty#f03064d8 = InputPeerNotifyEvents;
inputPeerNotifyEventsAll#e86a2c74 = InputPeerNotifyEvents;
inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = InputPeerNotifySettings;
inputPeerNotifySettings#38935eb2 flags:# show_previews:flags.0?true silent:flags.1?true mute_until:int sound:string = InputPeerNotifySettings;
peerNotifyEventsEmpty#add53cb3 = PeerNotifyEvents;
peerNotifyEventsAll#6d1ded88 = PeerNotifyEvents;
peerNotifySettingsEmpty#70a68512 = PeerNotifySettings;
peerNotifySettings#9acda4c0 flags:# show_previews:flags.0?true silent:flags.1?true mute_until:int sound:string = PeerNotifySettings;
peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings;
peerSettings#818426cd flags:# report_spam:flags.0?true = PeerSettings;
@@ -461,9 +461,9 @@ photos.photo#20212ca8 photo:Photo users:Vector<User> = photos.Photo;
upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes file_hashes:Vector<FileHash> = upload.File;
dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int = DcOption;
dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption;
config#86b5778e flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string suggested_lang_code:flags.2?string lang_pack_version:flags.2?int = Config;
config#eb7bb160 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string suggested_lang_code:flags.2?string lang_pack_version:flags.2?int = Config;
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
@@ -508,7 +508,6 @@ help.support#17c6b5f6 phone_number:string user:User = help.Support;
notifyPeer#9fd40bd8 peer:Peer = NotifyPeer;
notifyUsers#b4c83b4c = NotifyPeer;
notifyChats#c007cec3 = NotifyPeer;
notifyAll#74d07c60 = NotifyPeer;
sendMessageTypingAction#16bf744e = SendMessageAction;
sendMessageCancelAction#fd5ec8f5 = SendMessageAction;
@@ -561,7 +560,7 @@ documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
documentAttributeHasStickers#9801d2f7 = DocumentAttribute;
messages.stickersNotModified#f1749a22 = messages.Stickers;
messages.stickers#8a8ecd32 hash:string stickers:Vector<Document> = messages.Stickers;
messages.stickers#e4599bbd hash:int stickers:Vector<Document> = messages.Stickers;
stickerPack#12b299d4 emoticon:string documents:Vector<long> = StickerPack;
@@ -690,7 +689,7 @@ messages.savedGifs#2e0709a5 hash:int gifs:Vector<Document> = messages.SavedGifs;
inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaVenue#aaafadc8 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaContact#2daf01a7 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
@@ -702,7 +701,7 @@ inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:Input
botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaVenue#4366232e flags:# geo:GeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaContact#35edb4d4 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult;
@@ -841,6 +840,8 @@ webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string attributes:Vect
inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = InputWebDocument;
inputWebFileLocation#c239d686 url:string access_hash:long = InputWebFileLocation;
inputWebFileGeoPointLocation#66275a62 geo_point:InputGeoPoint w:int h:int zoom:int scale:int = InputWebFileLocation;
inputWebFileGeoMessageLocation#553f32eb peer:InputPeer msg_id:int w:int h:int zoom:int scale:int = InputWebFileLocation;
upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile;
@@ -955,15 +956,19 @@ messages.foundStickerSets#5108d648 hash:int sets:Vector<StickerSetCovered> = mes
fileHash#6242c773 offset:int limit:int hash:bytes = FileHash;
inputClientProxy#75588b3f address:string port:int = InputClientProxy;
help.proxyDataEmpty#e09e1fb8 expires:int = help.ProxyData;
help.proxyDataPromo#2bf7ee23 expires:int peer:Peer chats:Vector<Chat> users:Vector<User> = help.ProxyData;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X;
initConnection#c7481da6 {X:Type} api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string query:!X = X;
initConnection#785188b8 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X;
invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;
invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;
auth.checkPhone#6fe51dfb phone_number:string = auth.CheckedPhone;
auth.sendCode#86aef0ec flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool api_id:int api_hash:string = auth.SentCode;
auth.signUp#1b067634 phone_number:string phone_code_hash:string phone_code:string first_name:string last_name:string = auth.Authorization;
auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization;
@@ -1066,7 +1071,7 @@ messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long da
messages.receivedQueue#55a5bb66 max_qts:int = Vector<long>;
messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool;
messages.readMessageContents#36a73f77 id:Vector<int> = messages.AffectedMessages;
messages.getStickers#85cb5182 flags:# exclude_featured:flags.0?true emoticon:string hash:string = messages.Stickers;
messages.getStickers#43d4f2c emoticon:string hash:int = messages.Stickers;
messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers;
messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector<MessageEntity> = MessageMedia;
messages.exportChatInvite#7d885289 chat_id:int = ExportedChatInvite;
@@ -1090,8 +1095,8 @@ messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_p
messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector<InputBotInlineResult> cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool;
messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string = Updates;
messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData;
messages.editMessage#5d1b8dd flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true peer:InputPeer id:int message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Updates;
messages.editInlineBotMessage#b0e08243 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true id:InputBotInlineMessageID message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Bool;
messages.editMessage#c000e4c8 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Updates;
messages.editInlineBotMessage#adc3e828 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Bool;
messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer;
messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool;
messages.getPeerDialogs#e470bcfd peers:Vector<InputDialogPeer> = messages.PeerDialogs;
@@ -1153,10 +1158,11 @@ help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
help.getInviteText#4d392343 = help.InviteText;
help.getSupport#9cdf08cd = help.Support;
help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
help.getTermsOfService#350170f3 = help.TermsOfService;
help.getTermsOfService#8e59b7e7 country_iso2:string = help.TermsOfService;
help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
help.getCdnConfig#52029342 = CdnConfig;
help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls;
help.getProxyData#3d7758e1 = help.ProxyData;
channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
@@ -1220,4 +1226,4 @@ langpack.getStrings#2e1ee318 lang_code:string keys:Vector<string> = Vector<LangP
langpack.getDifference#b2e4d7d from_version:int = LangPackDifference;
langpack.getLanguages#800fd57d = Vector<LangPackLanguage>;
// LAYER 76
// LAYER 79

View File

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

View File

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

View File

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

View File

@@ -480,7 +480,7 @@ int main(int argc, char *argv[])
return 0;
}
QString countBetaVersionSignature(quint64 version) { // duplicated in autoupdate.cpp
QString countBetaVersionSignature(quint64 version) { // duplicated in autoupdater.cpp
QByteArray cBetaPrivateKey(BetaPrivateKey);
if (cBetaPrivateKey.isEmpty()) {
cout << "Error: Trying to count beta version signature without beta private key!\n";

View File

@@ -254,7 +254,7 @@ int main(int argc, const char * argv[]) {
forKey:NSWorkspaceLaunchConfigurationArguments]
error:&error];
if (!result) {
writeLog([@"Could not run application, error: " stringByAppendingString:error ? [error localizedDescription] : @"(nil)"]);
writeLog([[NSString stringWithFormat:@"Could not run application, error %ld: ", (long)[error code]] stringByAppendingString: error ? [error localizedDescription] : @"(nil)"]);
}
closeLog();
return result ? 0 : -1;

View File

@@ -50,6 +50,8 @@ namespace {
constexpr auto kReloadChannelMembersTimeout = 1000; // 1 second wait before reload members in channel after adding
constexpr auto kSaveCloudDraftTimeout = 1000; // save draft to the cloud with 1 sec extra delay
constexpr auto kSaveDraftBeforeQuitTimeout = 1500; // give the app 1.5 secs to save drafts to cloud when quitting
constexpr auto kProxyPromotionInterval = TimeId(60 * 60);
constexpr auto kProxyPromotionMinDelay = TimeId(10);
constexpr auto kSmallDelayMs = 5;
constexpr auto kUnreadMentionsPreloadIfLess = 5;
constexpr auto kUnreadMentionsFirstRequestLimit = 10;
@@ -60,6 +62,7 @@ constexpr auto kReadFeaturedSetsTimeout = TimeMs(1000);
constexpr auto kFileLoaderQueueStopTimeout = TimeMs(5000);
constexpr auto kFeedReadTimeout = TimeMs(1000);
constexpr auto kStickersByEmojiInvalidateTimeout = TimeMs(60 * 60 * 1000);
constexpr auto kNotifySettingSaveTimeout = TimeMs(1000);
bool IsSilentPost(not_null<HistoryItem*> item, bool silent) {
const auto history = item->history();
@@ -126,7 +129,7 @@ FileLoadTo FileLoadTaskOptions(const ApiWrap::SendOptions &options) {
const auto peer = options.history->peer;
return FileLoadTo(
peer->id,
peer->notifySilentPosts(),
Auth().data().notifySilentPosts(peer),
options.replyTo);
}
@@ -134,12 +137,14 @@ FileLoadTo FileLoadTaskOptions(const ApiWrap::SendOptions &options) {
ApiWrap::ApiWrap(not_null<AuthSession*> session)
: _session(session)
, _messageDataResolveDelayed([this] { resolveMessageDatas(); })
, _webPagesTimer([this] { resolveWebPages(); })
, _draftsSaveTimer([this] { saveDraftsToCloud(); })
, _featuredSetsReadTimer([this] { readFeaturedSets(); })
, _messageDataResolveDelayed([=] { resolveMessageDatas(); })
, _webPagesTimer([=] { resolveWebPages(); })
, _draftsSaveTimer([=] { saveDraftsToCloud(); })
, _featuredSetsReadTimer([=] { readFeaturedSets(); })
, _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout))
, _feedReadTimer([this] { readFeeds(); }) {
, _feedReadTimer([=] { readFeeds(); })
, _proxyPromotionTimer([=] { refreshProxyPromotion(); })
, _updateNotifySettingsTimer([=] { sendNotifySettingsUpdates(); }) {
}
void ApiWrap::requestChangelog(
@@ -152,6 +157,82 @@ void ApiWrap::requestChangelog(
).send();
}
void ApiWrap::refreshProxyPromotion() {
const auto now = unixtime();
const auto next = (_proxyPromotionNextRequestTime != 0)
? _proxyPromotionNextRequestTime
: now;
if (_proxyPromotionRequestId) {
getProxyPromotionDelayed(now, next);
return;
}
const auto proxy = Global::UseProxy()
? Global::SelectedProxy()
: ProxyData();
const auto key = [&]() -> std::pair<QString, uint32> {
if (!Global::UseProxy()) {
return {};
}
const auto &proxy = Global::SelectedProxy();
if (proxy.type != ProxyData::Type::Mtproto) {
return {};
}
return { proxy.host, proxy.port };
}();
if (_proxyPromotionKey == key && now < next) {
getProxyPromotionDelayed(now, next);
return;
}
_proxyPromotionKey = key;
if (key.first.isEmpty() || !key.second) {
proxyPromotionDone(MTP_help_proxyDataEmpty(
MTP_int(unixtime() + kProxyPromotionInterval)));
return;
}
_proxyPromotionRequestId = request(MTPhelp_GetProxyData(
)).done([=](const MTPhelp_ProxyData &result) {
_proxyPromotionRequestId = 0;
proxyPromotionDone(result);
}).fail([=](const RPCError &error) {
_proxyPromotionRequestId = 0;
const auto now = unixtime();
const auto next = _proxyPromotionNextRequestTime = now
+ kProxyPromotionInterval;
if (!_proxyPromotionTimer.isActive()) {
getProxyPromotionDelayed(now, next);
}
}).send();
}
void ApiWrap::getProxyPromotionDelayed(TimeId now, TimeId next) {
_proxyPromotionTimer.callOnce(std::min(
std::max(next - now, kProxyPromotionMinDelay),
kProxyPromotionInterval) * TimeMs(1000));
};
void ApiWrap::proxyPromotionDone(const MTPhelp_ProxyData &proxy) {
if (proxy.type() == mtpc_help_proxyDataEmpty) {
const auto &data = proxy.c_help_proxyDataEmpty();
const auto next = _proxyPromotionNextRequestTime = data.vexpires.v;
getProxyPromotionDelayed(unixtime(), next);
_session->data().setProxyPromoted(nullptr);
return;
}
Assert(proxy.type() == mtpc_help_proxyDataPromo);
const auto &data = proxy.c_help_proxyDataPromo();
const auto next = _proxyPromotionNextRequestTime = data.vexpires.v;
getProxyPromotionDelayed(unixtime(), next);
App::feedChats(data.vchats);
App::feedUsers(data.vusers);
const auto peerId = peerFromMTP(data.vpeer);
const auto peer = App::peer(peerId);
_session->data().setProxyPromoted(peer);
if (const auto history = App::historyLoaded(peer)) {
_session->api().requestDialogEntry(history);
}
}
void ApiWrap::applyUpdates(
const MTPUpdates &updates,
uint64 sentMessageRandomId) {
@@ -194,7 +275,7 @@ void ApiWrap::savePinnedOrder() {
// )).done([=](const MTPUpdates &result) {
// applyUpdates(result);
// if (group) {
// channel->setFeed(Auth().data().feed(feedId));
// channel->setFeed(_session->data().feed(feedId));
// } else {
// channel->clearFeed();
// }
@@ -463,7 +544,7 @@ void ApiWrap::applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs) {
//case mtpc_dialogFeed: { // #feed
// const auto &fields = dialog.c_dialogFeed();
// const auto feed = Auth().data().feed(fields.vfeed_id.v);
// const auto feed = _session->data().feed(fields.vfeed_id.v);
// feed->applyDialog(fields);
//} break;
}
@@ -908,7 +989,7 @@ void ApiWrap::requestPeers(const QList<PeerData*> &peers) {
channels.push_back((*i)->asChannel()->inputChannel);
}
}
auto handleChats = [this](const MTPmessages_Chats &result) {
auto handleChats = [=](const MTPmessages_Chats &result) {
if (auto chats = Api::getChatsFromMessagesChats(result)) {
App::feedChats(*chats);
}
@@ -920,7 +1001,7 @@ void ApiWrap::requestPeers(const QList<PeerData*> &peers) {
request(MTPchannels_GetChannels(MTP_vector<MTPInputChannel>(channels))).done(handleChats).send();
}
if (!users.isEmpty()) {
request(MTPusers_GetUsers(MTP_vector<MTPInputUser>(users))).done([this](const MTPVector<MTPUser> &result) {
request(MTPusers_GetUsers(MTP_vector<MTPInputUser>(users))).done([=](const MTPVector<MTPUser> &result) {
App::feedUsers(result);
}).send();
}
@@ -1297,7 +1378,7 @@ void ApiWrap::deleteAllFromUser(
}
}
Auth().data().sendHistoryChangeNotifications();
_session->data().sendHistoryChangeNotifications();
deleteAllFromUserSend(channel, from);
}
@@ -1314,7 +1395,7 @@ void ApiWrap::deleteAllFromUserSend(
deleteAllFromUserSend(channel, from);
} else if (const auto history = App::historyLoaded(channel)) {
if (!history->lastMessageKnown()) {
Auth().api().requestDialogEntry(history);
requestDialogEntry(history);
}
}
}).send();
@@ -1509,16 +1590,28 @@ void ApiWrap::stickerSetDisenabled(mtpRequestId requestId) {
}
};
void ApiWrap::joinChannel(ChannelData *channel) {
void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
if (channel->amIn()) {
Notify::peerUpdatedDelayed(channel, Notify::PeerUpdate::Flag::ChannelAmIn);
Notify::peerUpdatedDelayed(
channel,
Notify::PeerUpdate::Flag::ChannelAmIn);
} else if (!_channelAmInRequests.contains(channel)) {
auto requestId = request(MTPchannels_JoinChannel(channel->inputChannel)).done([this, channel](const MTPUpdates &result) {
auto requestId = request(MTPchannels_JoinChannel(
channel->inputChannel
)).done([=](const MTPUpdates &result) {
_channelAmInRequests.remove(channel);
applyUpdates(result);
}).fail([this, channel](const RPCError &error) {
if (error.type() == qstr("CHANNELS_TOO_MUCH")) {
}).fail([=](const RPCError &error) {
if (error.type() == qstr("CHANNEL_PRIVATE")
|| error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA")
|| error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
Ui::show(Box<InformBox>(lang(channel->isMegagroup()
? lng_group_not_accessible
: lng_channel_not_accessible)));
} else if (error.type() == qstr("CHANNELS_TOO_MUCH")) {
Ui::show(Box<InformBox>(lang(lng_join_channel_error)));
} else if (error.type() == qstr("USERS_TOO_MUCH")) {
Ui::show(Box<InformBox>(lang(lng_group_full)));
}
_channelAmInRequests.remove(channel);
}).send();
@@ -1527,14 +1620,18 @@ void ApiWrap::joinChannel(ChannelData *channel) {
}
}
void ApiWrap::leaveChannel(ChannelData *channel) {
void ApiWrap::leaveChannel(not_null<ChannelData*> channel) {
if (!channel->amIn()) {
Notify::peerUpdatedDelayed(channel, Notify::PeerUpdate::Flag::ChannelAmIn);
Notify::peerUpdatedDelayed(
channel,
Notify::PeerUpdate::Flag::ChannelAmIn);
} else if (!_channelAmInRequests.contains(channel)) {
auto requestId = request(MTPchannels_LeaveChannel(channel->inputChannel)).done([this, channel](const MTPUpdates &result) {
auto requestId = request(MTPchannels_LeaveChannel(
channel->inputChannel
)).done([=](const MTPUpdates &result) {
_channelAmInRequests.remove(channel);
applyUpdates(result);
}).fail([this, channel](const RPCError &error) {
}).fail([=](const RPCError &error) {
_channelAmInRequests.remove(channel);
}).send();
@@ -1542,80 +1639,137 @@ void ApiWrap::leaveChannel(ChannelData *channel) {
}
}
void ApiWrap::blockUser(UserData *user) {
void ApiWrap::blockUser(not_null<UserData*> user) {
if (user->isBlocked()) {
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserIsBlocked);
} else if (!_blockRequests.contains(user)) {
} else if (_blockRequests.find(user) == end(_blockRequests)) {
auto requestId = request(MTPcontacts_Block(user->inputUser)).done([this, user](const MTPBool &result) {
_blockRequests.remove(user);
_blockRequests.erase(user);
user->setBlockStatus(UserData::BlockStatus::Blocked);
}).fail([this, user](const RPCError &error) {
_blockRequests.remove(user);
_blockRequests.erase(user);
}).send();
_blockRequests.insert(user, requestId);
_blockRequests.emplace(user, requestId);
}
}
void ApiWrap::unblockUser(UserData *user) {
void ApiWrap::unblockUser(not_null<UserData*> user) {
if (!user->isBlocked()) {
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserIsBlocked);
} else if (!_blockRequests.contains(user)) {
} else if (_blockRequests.find(user) == end(_blockRequests)) {
auto requestId = request(MTPcontacts_Unblock(user->inputUser)).done([this, user](const MTPBool &result) {
_blockRequests.remove(user);
_blockRequests.erase(user);
user->setBlockStatus(UserData::BlockStatus::NotBlocked);
}).fail([this, user](const RPCError &error) {
_blockRequests.remove(user);
_blockRequests.erase(user);
}).send();
_blockRequests.insert(user, requestId);
_blockRequests.emplace(user, requestId);
}
}
void ApiWrap::exportInviteLink(PeerData *peer) {
if (_exportInviteRequests.contains(peer)) {
void ApiWrap::exportInviteLink(not_null<PeerData*> peer) {
if (_exportInviteRequests.find(peer) != end(_exportInviteRequests)) {
return;
}
auto sendRequest = [this, peer] {
auto exportFail = [this, peer](const RPCError &error) {
_exportInviteRequests.remove(peer);
const auto sendRequest = [this, peer] {
const auto exportFail = [this, peer](const RPCError &error) {
_exportInviteRequests.erase(peer);
};
if (auto chat = peer->asChat()) {
return request(MTPmessages_ExportChatInvite(chat->inputChat)).done([this, chat](const MTPExportedChatInvite &result) {
_exportInviteRequests.remove(chat);
chat->setInviteLink((result.type() == mtpc_chatInviteExported) ? qs(result.c_chatInviteExported().vlink) : QString());
if (const auto chat = peer->asChat()) {
return request(MTPmessages_ExportChatInvite(
chat->inputChat
)).done([=](const MTPExportedChatInvite &result) {
_exportInviteRequests.erase(chat);
chat->setInviteLink(
(result.type() == mtpc_chatInviteExported
? qs(result.c_chatInviteExported().vlink)
: QString()));
}).fail(exportFail).send();
} else if (auto channel = peer->asChannel()) {
return request(MTPchannels_ExportInvite(channel->inputChannel)).done([this, channel](const MTPExportedChatInvite &result) {
_exportInviteRequests.remove(channel);
channel->setInviteLink((result.type() == mtpc_chatInviteExported) ? qs(result.c_chatInviteExported().vlink) : QString());
} else if (const auto channel = peer->asChannel()) {
return request(MTPchannels_ExportInvite(
channel->inputChannel
)).done([=](const MTPExportedChatInvite &result) {
_exportInviteRequests.erase(channel);
channel->setInviteLink(
(result.type() == mtpc_chatInviteExported
? qs(result.c_chatInviteExported().vlink)
: QString()));
}).fail(exportFail).send();
}
return 0;
};
if (auto requestId = sendRequest()) {
_exportInviteRequests.insert(peer, requestId);
if (const auto requestId = sendRequest()) {
_exportInviteRequests.emplace(peer, requestId);
}
}
void ApiWrap::requestNotifySetting(PeerData *peer) {
if (_notifySettingRequests.contains(peer)) return;
auto notifyPeer = MTP_inputNotifyPeer(peer->input);
auto requestId = request(MTPaccount_GetNotifySettings(notifyPeer)).done([this, notifyPeer, peer](const MTPPeerNotifySettings &result) {
notifySettingReceived(notifyPeer, result);
_notifySettingRequests.remove(peer);
}).fail([this, notifyPeer, peer](const RPCError &error) {
notifySettingReceived(notifyPeer, MTP_peerNotifySettingsEmpty());
_notifySettingRequests.remove(peer);
void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) {
const auto key = [&] {
switch (peer.type()) {
case mtpc_inputNotifyUsers: return peerFromUser(0);
case mtpc_inputNotifyChats: return peerFromChat(0);
case mtpc_inputNotifyPeer: {
const auto &inner = peer.c_inputNotifyPeer().vpeer;
switch (inner.type()) {
case mtpc_inputPeerSelf:
return _session->userPeerId();
case mtpc_inputPeerEmpty:
return PeerId(0);
case mtpc_inputPeerChannel:
return peerFromChannel(
inner.c_inputPeerChannel().vchannel_id);
case mtpc_inputPeerChat:
return peerFromChat(inner.c_inputPeerChat().vchat_id);
case mtpc_inputPeerUser:
return peerFromUser(inner.c_inputPeerUser().vuser_id);
}
Unexpected("Type in ApiRequest::requestNotifySettings peer.");
} break;
}
Unexpected("Type in ApiRequest::requestNotifySettings.");
}();
if (_notifySettingRequests.find(key) != end(_notifySettingRequests)) {
return;
}
auto requestId = request(MTPaccount_GetNotifySettings(
peer
)).done([=](const MTPPeerNotifySettings &result) {
notifySettingReceived(peer, result);
_notifySettingRequests.erase(key);
}).fail([=](const RPCError &error) {
notifySettingReceived(peer, MTP_peerNotifySettings(
MTP_flags(0),
MTPBool(),
MTPBool(),
MTPint(),
MTPstring()));
_notifySettingRequests.erase(key);
}).send();
_notifySettingRequests.insert(peer, requestId);
_notifySettingRequests.emplace(key, requestId);
}
void ApiWrap::saveDraftToCloudDelayed(History *history) {
_draftsSaveRequestIds.insert(history, 0);
void ApiWrap::updateNotifySettingsDelayed(not_null<const PeerData*> peer) {
_updateNotifySettingsPeers.emplace(peer);
_updateNotifySettingsTimer.callOnce(kNotifySettingSaveTimeout);
}
void ApiWrap::sendNotifySettingsUpdates() {
while (!_updateNotifySettingsPeers.empty()) {
const auto peer = *_updateNotifySettingsPeers.begin();
_updateNotifySettingsPeers.erase(_updateNotifySettingsPeers.begin());
request(MTPaccount_UpdateNotifySettings(
MTP_inputNotifyPeer(peer->input),
peer->notifySerialize()
)).afterDelay(_updateNotifySettingsPeers.empty() ? 0 : 10).send();
}
}
void ApiWrap::saveDraftToCloudDelayed(not_null<History*> history) {
_draftsSaveRequestIds.emplace(history, 0);
if (!_draftsSaveTimer.isActive()) {
_draftsSaveTimer.callOnce(kSaveCloudDraftTimeout);
}
@@ -1631,6 +1785,7 @@ void ApiWrap::savePrivacy(const MTPInputPrivacyKey &key, QVector<MTPInputPrivacy
auto requestId = request(MTPaccount_SetPrivacy(key, MTP_vector<MTPInputPrivacyRule>(std::move(rules)))).done([this, keyTypeId](const MTPaccount_PrivacyRules &result) {
Expects(result.type() == mtpc_account_privacyRules);
auto &rules = result.c_account_privacyRules();
App::feedUsers(rules.vusers);
_privacySaveRequests.remove(keyTypeId);
@@ -1686,7 +1841,7 @@ void ApiWrap::handlePrivacyChange(mtpTypeId keyTypeId, const MTPVector<MTPPrivac
}
auto now = unixtime();
App::enumerateUsers([&userRules, contactsRule, everyoneRule, now](UserData *user) {
App::enumerateUsers([&](UserData *user) {
if (user->isSelf() || user->loadedStatus != PeerData::FullLoaded) {
return;
}
@@ -1806,9 +1961,9 @@ void ApiWrap::applyAffectedMessages(
void ApiWrap::saveDraftsToCloud() {
for (auto i = _draftsSaveRequestIds.begin(), e = _draftsSaveRequestIds.end(); i != e; ++i) {
if (i.value()) continue; // sent already
if (i->second) continue; // sent already
auto history = i.key();
auto history = i->first;
auto cloudDraft = history->cloudDraft();
auto localDraft = history->localDraft();
if (cloudDraft && cloudDraft->saveRequestId) {
@@ -1837,8 +1992,8 @@ void ApiWrap::saveDraftsToCloud() {
}
}
auto i = _draftsSaveRequestIds.find(history);
if (i != _draftsSaveRequestIds.cend() && i.value() == requestId) {
_draftsSaveRequestIds.remove(history);
if (i != _draftsSaveRequestIds.cend() && i->second == requestId) {
_draftsSaveRequestIds.erase(history);
checkQuitPreventFinished();
}
}).fail([this, history](const RPCError &error, mtpRequestId requestId) {
@@ -1848,18 +2003,18 @@ void ApiWrap::saveDraftsToCloud() {
}
}
auto i = _draftsSaveRequestIds.find(history);
if (i != _draftsSaveRequestIds.cend() && i.value() == requestId) {
_draftsSaveRequestIds.remove(history);
if (i != _draftsSaveRequestIds.cend() && i->second == requestId) {
_draftsSaveRequestIds.erase(history);
checkQuitPreventFinished();
}
}).send();
i.value() = cloudDraft->saveRequestId;
i->second = cloudDraft->saveRequestId;
}
}
bool ApiWrap::isQuitPrevent() {
if (_draftsSaveRequestIds.isEmpty()) {
if (_draftsSaveRequestIds.empty()) {
return false;
}
LOG(("ApiWrap prevents quit, saving drafts..."));
@@ -1868,7 +2023,7 @@ bool ApiWrap::isQuitPrevent() {
}
void ApiWrap::checkQuitPreventFinished() {
if (_draftsSaveRequestIds.isEmpty()) {
if (_draftsSaveRequestIds.empty()) {
if (App::quitting()) {
LOG(("ApiWrap doesn't prevent quit any more."));
}
@@ -1876,41 +2031,43 @@ void ApiWrap::checkQuitPreventFinished() {
}
}
PeerData *ApiWrap::notifySettingReceived(
void ApiWrap::notifySettingReceived(
MTPInputNotifyPeer notifyPeer,
const MTPPeerNotifySettings &settings) {
PeerData *requestedPeer = nullptr;
switch (notifyPeer.type()) {
case mtpc_inputNotifyAll:
App::main()->applyNotifySetting(MTP_notifyAll(), settings);
break;
case mtpc_inputNotifyUsers:
App::main()->applyNotifySetting(MTP_notifyUsers(), settings);
_session->data().applyNotifySetting(MTP_notifyUsers(), settings);
break;
case mtpc_inputNotifyChats:
App::main()->applyNotifySetting(MTP_notifyChats(), settings);
_session->data().applyNotifySetting(MTP_notifyChats(), settings);
break;
case mtpc_inputNotifyPeer: {
auto &peer = notifyPeer.c_inputNotifyPeer().vpeer;
switch (peer.type()) {
case mtpc_inputPeerEmpty: App::main()->applyNotifySetting(
MTP_notifyPeer(MTP_peerUser(MTP_int(0))),
settings);
break;
case mtpc_inputPeerSelf: requestedPeer = App::self(); break;
case mtpc_inputPeerUser: requestedPeer = App::user(peerFromUser(peer.c_inputPeerUser().vuser_id)); break;
case mtpc_inputPeerChat: requestedPeer = App::chat(peerFromChat(peer.c_inputPeerChat().vchat_id)); break;
case mtpc_inputPeerChannel: requestedPeer = App::channel(peerFromChannel(peer.c_inputPeerChannel().vchannel_id)); break;
}
if (requestedPeer) {
App::main()->applyNotifySetting(
MTP_notifyPeer(peerToMTP(requestedPeer->id)),
const auto apply = [&](PeerId peerId) {
_session->data().applyNotifySetting(
MTP_notifyPeer(peerToMTP(peerId)),
settings);
};
switch (peer.type()) {
case mtpc_inputPeerEmpty:
apply(0);
break;
case mtpc_inputPeerSelf:
apply(_session->userPeerId());
break;
case mtpc_inputPeerUser:
apply(peerFromUser(peer.c_inputPeerUser().vuser_id));
break;
case mtpc_inputPeerChat:
apply(peerFromChat(peer.c_inputPeerChat().vchat_id));
break;
case mtpc_inputPeerChannel:
apply(peerFromChannel(peer.c_inputPeerChannel().vchannel_id));
break;
}
} break;
}
_session->notifications().checkDelayed();
return requestedPeer;
}
void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) {
@@ -2017,7 +2174,7 @@ void ApiWrap::requestParticipantsCountDelayed(
not_null<ChannelData*> channel) {
_participantsCountRequestTimer.call(
kReloadChannelMembersTimeout,
[this, channel] { channel->updateFullForced(); });
[=] { channel->updateFullForced(); });
}
void ApiWrap::requestChannelRangeDifference(not_null<History*> history) {
@@ -2227,11 +2384,10 @@ std::vector<not_null<DocumentData*>> *ApiWrap::stickersByEmoji(
if (sendRequest) {
const auto hash = (it != _stickersByEmoji.end())
? it->second.hash
: QString();
: int32(0);
request(MTPmessages_GetStickers(
MTP_flags(MTPmessages_GetStickers::Flag::f_exclude_featured),
MTP_string(emoji->text()),
MTP_string(hash)
MTP_int(hash)
)).done([=](const MTPmessages_Stickers &result) {
if (result.type() == mtpc_messages_stickersNotModified) {
return;
@@ -2242,12 +2398,12 @@ std::vector<not_null<DocumentData*>> *ApiWrap::stickersByEmoji(
entry.list.clear();
entry.list.reserve(data.vstickers.v.size());
for (const auto &sticker : data.vstickers.v) {
const auto document = Auth().data().document(sticker);
const auto document = _session->data().document(sticker);
if (document->sticker()) {
entry.list.push_back(document);
}
}
entry.hash = qs(data.vhash);
entry.hash = data.vhash.v;
entry.received = getms(true);
_session->data().notifyStickersUpdated();
}).send();
@@ -2280,7 +2436,7 @@ void ApiWrap::requestStickers(TimeId now) {
};
_stickersUpdateRequest = request(MTPmessages_GetAllStickers(
MTP_int(Local::countStickersHash(true))
)).done(onDone).fail([this, onDone](const RPCError &error) {
)).done(onDone).fail([=](const RPCError &error) {
LOG(("App Fail: Failed to get stickers!"));
onDone(MTP_messages_allStickersNotModified());
}).send();
@@ -3392,7 +3548,8 @@ void ApiWrap::forwardMessages(
readServerHistory(history);
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
const auto silentPost = channelPost && peer->notifySilentPosts();
const auto silentPost = channelPost
&& _session->data().notifySilentPosts(peer);
auto flags = MTPDmessage::Flags(0);
auto sendFlags = MTPmessages_ForwardMessages::Flags(0);
@@ -3567,7 +3724,13 @@ void ApiWrap::sendSharedContact(
MTP_string(phone),
MTP_string(firstName),
MTP_string(lastName));
sendMedia(item, media, peer->notifySilentPosts());
sendMedia(item, media, _session->data().notifySilentPosts(peer));
if (const auto main = App::main()) {
_session->data().sendHistoryChangeNotifications();
main->historyToDown(history);
main->dialogsToUp();
}
}
void ApiWrap::sendVoiceMessage(
@@ -3575,7 +3738,7 @@ void ApiWrap::sendVoiceMessage(
VoiceWaveform waveform,
int duration,
const SendOptions &options) {
const auto caption = QString();
const auto caption = TextWithTags();
const auto to = FileLoadTaskOptions(options);
_fileLoader->addTask(std::make_unique<FileLoadTask>(
result,
@@ -3588,16 +3751,16 @@ void ApiWrap::sendVoiceMessage(
void ApiWrap::sendFiles(
Storage::PreparedList &&list,
SendMediaType type,
QString caption,
TextWithTags &&caption,
std::shared_ptr<SendingAlbum> album,
const SendOptions &options) {
if (list.files.size() > 1 && !caption.isEmpty()) {
if (list.files.size() > 1 && !caption.text.isEmpty()) {
auto message = MainWidget::MessageToSend(options.history);
message.textWithTags = { caption, TextWithTags::Tags() };
message.textWithTags = std::move(caption);
message.replyTo = options.replyTo;
message.clearDraft = false;
App::main()->sendMessage(message);
caption = QString();
caption = TextWithTags();
}
const auto to = FileLoadTaskOptions(options);
@@ -3642,7 +3805,7 @@ void ApiWrap::sendFile(
SendMediaType type,
const SendOptions &options) {
auto to = FileLoadTaskOptions(options);
auto caption = QString();
auto caption = TextWithTags();
_fileLoader->addTask(std::make_unique<FileLoadTask>(
QString(),
fileContent,
@@ -3716,7 +3879,7 @@ void ApiWrap::uploadAlbumMedia(
const MessageGroupId &groupId,
const MTPInputMedia &media) {
const auto localId = item->fullId();
const auto failed = [this] {
const auto failed = [=] {
};
request(MTPmessages_UploadMedia(
@@ -3726,6 +3889,7 @@ void ApiWrap::uploadAlbumMedia(
const auto item = App::histItemById(localId);
if (!item) {
failed();
return;
}
if (const auto media = item->media()) {
if (const auto photo = media->photo()) {
@@ -3870,7 +4034,7 @@ void ApiWrap::sendAlbumWithCancelled(
const MessageGroupId &groupId) {
const auto localId = item->fullId();
const auto albumIt = _sendingAlbums.find(groupId.raw());
if (albumIt != _sendingAlbums.end()) {
if (albumIt == _sendingAlbums.end()) {
// Sometimes we destroy item being sent already after the album
// was sent successfully. For example the message could be loaded
// from server (by messages.getHistory or updateNewMessage) and

View File

@@ -23,7 +23,7 @@ struct SendingAlbum;
enum class SendMediaType;
namespace Storage {
enum class SharedMediaType : char;
enum class SharedMediaType : signed char;
struct PreparedList;
} // namespace Storage
@@ -93,6 +93,7 @@ public:
void requestChangelog(
const QString &sinceVersion,
base::lambda<void(const MTPUpdates &result)> callback);
void refreshProxyPromotion();
void requestChannelMembersForAdd(
not_null<ChannelData*> channel,
@@ -133,16 +134,16 @@ public:
std::vector<not_null<DocumentData*>> *stickersByEmoji(
not_null<EmojiPtr> emoji);
void joinChannel(ChannelData *channel);
void leaveChannel(ChannelData *channel);
void joinChannel(not_null<ChannelData*> channel);
void leaveChannel(not_null<ChannelData*> channel);
void blockUser(UserData *user);
void unblockUser(UserData *user);
void blockUser(not_null<UserData*> user);
void unblockUser(not_null<UserData*> user);
void exportInviteLink(PeerData *peer);
void requestNotifySetting(PeerData *peer);
void saveDraftToCloudDelayed(History *history);
void exportInviteLink(not_null<PeerData*> peer);
void requestNotifySettings(const MTPInputNotifyPeer &peer);
void updateNotifySettingsDelayed(not_null<const PeerData*> peer);
void saveDraftToCloudDelayed(not_null<History*> history);
void savePrivacy(const MTPInputPrivacyKey &key, QVector<MTPInputPrivacyRule> &&rules);
void handlePrivacyChange(mtpTypeId keyTypeId, const MTPVector<MTPPrivacyRule> &rules);
@@ -252,7 +253,7 @@ public:
void sendFiles(
Storage::PreparedList &&list,
SendMediaType type,
QString caption,
TextWithTags &&caption,
std::shared_ptr<SendingAlbum> album,
const SendOptions &options);
void sendFile(
@@ -284,7 +285,7 @@ private:
struct StickersByEmoji {
std::vector<not_null<DocumentData*>> list;
QString hash;
int32 hash = 0;
TimeMs received = 0;
};
@@ -337,7 +338,7 @@ private:
MsgRange range,
const MTPupdates_ChannelDifference &result);
PeerData *notifySettingReceived(
void notifySettingReceived(
MTPInputNotifyPeer peer,
const MTPPeerNotifySettings &settings);
@@ -359,7 +360,6 @@ private:
not_null<ChannelData*> channel,
const QVector<MTPChannelParticipant> &participants);
void jumpToHistoryDate(not_null<PeerData*> peer, const QDate &date);
void jumpToFeedDate(not_null<Data::Feed*> feed, const QDate &date);
template <typename Callback>
@@ -437,6 +437,11 @@ private:
void readFeeds();
void getProxyPromotionDelayed(TimeId now, TimeId next);
void proxyPromotionDone(const MTPhelp_ProxyData &proxy);
void sendNotifySettingsUpdates();
not_null<AuthSession*> _session;
MessageDataRequests _messageDataRequests;
@@ -477,12 +482,10 @@ private:
QMap<uint64, QPair<uint64, mtpRequestId> > _stickerSetRequests;
QMap<ChannelData*, mtpRequestId> _channelAmInRequests;
QMap<UserData*, mtpRequestId> _blockRequests;
QMap<PeerData*, mtpRequestId> _exportInviteRequests;
QMap<PeerData*, mtpRequestId> _notifySettingRequests;
QMap<History*, mtpRequestId> _draftsSaveRequestIds;
std::map<not_null<UserData*>, mtpRequestId> _blockRequests;
std::map<not_null<PeerData*>, mtpRequestId> _exportInviteRequests;
std::map<PeerId, mtpRequestId> _notifySettingRequests;
std::map<not_null<History*>, mtpRequestId> _draftsSaveRequestIds;
base::Timer _draftsSaveTimer;
base::flat_set<mtpRequestId> _stickerSetDisenableRequests;
@@ -567,4 +570,12 @@ private:
base::flat_map<not_null<Data::Feed*>, mtpRequestId> _feedReadRequests;
base::Timer _feedReadTimer;
mtpRequestId _proxyPromotionRequestId = 0;
std::pair<QString, uint32> _proxyPromotionKey;
TimeId _proxyPromotionNextRequestTime = TimeId(0);
base::Timer _proxyPromotionTimer;
base::flat_set<not_null<const PeerData*>> _updateNotifySettingsPeers;
base::Timer _updateNotifySettingsTimer;
};

View File

@@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "observer_peer.h"
#include "auth_session.h"
#include "core/crash_reports.h"
#include "core/update_checker.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "window/themes/window_theme.h"
@@ -52,9 +53,6 @@ namespace {
using PeersData = QHash<PeerId, PeerData*>;
PeersData peersData;
using MutedPeers = QMap<not_null<PeerData*>, bool>;
MutedPeers mutedPeers;
using LocationsData = QHash<LocationCoords, LocationData*>;
LocationsData locationsData;
@@ -167,9 +165,6 @@ namespace {
cSetOtherOnline(0);
clearStorageImages();
if (auto w = wnd()) {
w->updateConnectingStatus();
}
return true;
}
} // namespace
@@ -923,6 +918,7 @@ namespace {
: nullptr);
existing->setViewsCount(m.has_views() ? m.vviews.v : -1);
existing->indexAsNewItem();
Auth().data().requestItemTextRefresh(existing);
if (existing->mainView()) {
App::checkSavedGif(existing);
return true;
@@ -1154,14 +1150,23 @@ namespace {
return i.value();
}
void enumerateUsers(base::lambda<void(UserData*)> action) {
for_const (auto peer, peersData) {
if (auto user = peer->asUser()) {
void enumerateUsers(base::lambda<void(not_null<UserData*>)> action) {
for_const (const auto peer, peersData) {
if (const auto user = peer->asUser()) {
action(user);
}
}
}
void enumerateChatsChannels(
base::lambda<void(not_null<PeerData*>)> action) {
for_const (const auto peer, peersData) {
if (!peer->isUser()) {
action(peer);
}
}
}
UserData *self() {
return ::self;
}
@@ -1310,7 +1315,6 @@ namespace {
void historyClearItems() {
randomData.clear();
sentData.clear();
mutedPeers.clear();
cSetSavedPeers(SavedPeers());
cSetSavedPeersByTime(SavedPeersByTime());
cSetRecentInlineBots(RecentInlineBots());
@@ -1627,6 +1631,7 @@ namespace {
int64 nowImageCacheSize = imageCacheSize();
if (nowImageCacheSize > serviceImageCacheSize + MemoryForImageCache) {
App::forgetMedia();
Auth().data().forgetMedia();
serviceImageCacheSize = imageCacheSize();
}
}
@@ -1665,7 +1670,7 @@ namespace {
void restart() {
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
bool updateReady = (Sandbox::updatingState() == Application::UpdatingReady);
bool updateReady = (Core::UpdateChecker().state() == Core::UpdateChecker::State::Ready);
#else // !TDESKTOP_DISABLE_AUTOUPDATE
bool updateReady = false;
#endif // else for !TDESKTOP_DISABLE_AUTOUPDATE
@@ -1748,70 +1753,6 @@ namespace {
return QPixmap::fromImage(std::move(image), Qt::ColorOnly);
}
void regMuted(not_null<PeerData*> peer, TimeMs changeIn) {
::mutedPeers.insert(peer, true);
App::main()->updateMutedIn(changeIn);
}
void unregMuted(not_null<PeerData*> peer) {
::mutedPeers.remove(peer);
}
void updateMuted() {
auto changeInMin = TimeMs(0);
for (auto i = ::mutedPeers.begin(); i != ::mutedPeers.end();) {
const auto history = App::historyLoaded(i.key()->id);
const auto muteFinishesIn = i.key()->notifyMuteFinishesIn();
if (muteFinishesIn > 0) {
if (history) {
history->changeMute(true);
}
if (!changeInMin || muteFinishesIn < changeInMin) {
changeInMin = muteFinishesIn;
}
++i;
} else {
if (history) {
history->changeMute(false);
}
i = ::mutedPeers.erase(i);
}
}
if (changeInMin) App::main()->updateMutedIn(changeInMin);
}
void setProxySettings(QNetworkAccessManager &manager) {
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
manager.setProxy(getHttpProxySettings());
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
}
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
QNetworkProxy getHttpProxySettings() {
const ProxyData *proxy = nullptr;
if (Global::started()) {
proxy = (Global::ConnectionType() == dbictHttpProxy) ? (&Global::ConnectionProxy()) : nullptr;
} else {
proxy = Sandbox::PreLaunchProxy().host.isEmpty() ? nullptr : (&Sandbox::PreLaunchProxy());
}
if (proxy) {
return QNetworkProxy(QNetworkProxy::HttpProxy, proxy->host, proxy->port, proxy->user, proxy->password);
}
return QNetworkProxy(QNetworkProxy::DefaultProxy);
}
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
void setProxySettings(QTcpSocket &socket) {
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
if (Global::ConnectionType() == dbictTcpProxy) {
auto &p = Global::ConnectionProxy();
socket.setProxy(QNetworkProxy(QNetworkProxy::Socks5Proxy, p.host, p.port, p.user, p.password));
} else {
socket.setProxy(QNetworkProxy(QNetworkProxy::NoProxy));
}
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
}
void rectWithCorners(Painter &p, QRect rect, const style::color &bg, RoundCorners index, RectParts corners) {
auto parts = RectPart::Top
| RectPart::NoTopBottom

View File

@@ -135,7 +135,9 @@ namespace App {
inline ChannelData *channelLoaded(ChannelId channelId) {
return channel(channelId, PeerData::FullLoaded);
}
void enumerateUsers(base::lambda<void(UserData*)> action);
void enumerateUsers(base::lambda<void(not_null<UserData*>)> action);
void enumerateChatsChannels(
base::lambda<void(not_null<PeerData*>)> action);
UserData *self();
PeerData *peerByName(const QString &username);
@@ -221,16 +223,6 @@ namespace App {
QImage readImage(const QString &file, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr, QByteArray *content = 0);
QPixmap pixmapFromImageInPlace(QImage &&image);
void regMuted(not_null<PeerData*> peer, TimeMs changeIn);
void unregMuted(not_null<PeerData*> peer);
void updateMuted();
void setProxySettings(QNetworkAccessManager &manager);
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
QNetworkProxy getHttpProxySettings();
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
void setProxySettings(QTcpSocket &socket);
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners);
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners);

View File

@@ -11,11 +11,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwidget.h"
#include "mainwindow.h"
#include "storage/localstorage.h"
#include "autoupdater.h"
#include "window/notifications_manager.h"
#include "core/crash_reports.h"
#include "messenger.h"
#include "base/timer.h"
#include "base/qthelp_url.h"
#include "base/qthelp_regex.h"
#include "core/update_checker.h"
#include "core/crash_report_window.h"
namespace {
@@ -65,7 +67,11 @@ Application::Application(
int &argc,
char **argv)
: QApplication(argc, argv)
, _launcher(launcher) {
, _launcher(launcher)
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
, _updateChecker(std::make_unique<Core::UpdateChecker>())
#endif // TDESKTOP_DISABLE_AUTOUPDATE
{
const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath());
char h[33] = { 0 };
hashMd5Hex(d.constData(), d.size(), h);
@@ -86,13 +92,6 @@ Application::Application(
QTimer::singleShot(0, this, SLOT(startApplication()));
connect(this, SIGNAL(aboutToQuit()), this, SLOT(closeApplication()));
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
_updateCheckTimer.create(this);
connect(_updateCheckTimer, SIGNAL(timeout()), this, SLOT(updateCheck()));
connect(this, SIGNAL(updateFailed()), this, SLOT(onUpdateFailed()));
connect(this, SIGNAL(updateReady()), this, SLOT(onUpdateReady()));
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
if (cManyInstance()) {
LOG(("Many instance allowed, starting..."));
singleInstanceChecked();
@@ -180,7 +179,7 @@ void Application::socketError(QLocalSocket::LocalSocketError e) {
#endif // !Q_OS_WINRT
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
if (!cNoStartUpdate() && checkReadyUpdate()) {
if (!cNoStartUpdate() && Core::checkReadyUpdate()) {
cSetRestartingUpdate(true);
DEBUG_LOG(("Application Info: installing update instead of starting app..."));
return App::quit();
@@ -196,6 +195,7 @@ void Application::singleInstanceChecked() {
}
Sandbox::start();
refreshGlobalProxy();
if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
new NotStartedWindow();
@@ -307,9 +307,30 @@ void Application::startApplication() {
void Application::createMessenger() {
Expects(!App::quitting());
_messengerInstance = std::make_unique<Messenger>(_launcher);
}
void Application::refreshGlobalProxy() {
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
const auto proxy = [&] {
if (Global::started()) {
return Global::UseProxy()
? Global::SelectedProxy()
: ProxyData();
}
return Sandbox::PreLaunchProxy();
}();
if (proxy.type == ProxyData::Type::Socks5
|| proxy.type == ProxyData::Type::Http) {
QNetworkProxy::setApplicationProxy(
ToNetworkProxy(ToDirectIpProxy(proxy)));
} else {
QNetworkProxyFactory::setUseSystemConfiguration(true);
}
#endif // TDESKTOP_DISABLE_NETWORK_PROXY
}
void Application::closeApplication() {
if (App::launchState() == App::QuitProcessed) return;
App::setLaunchState(App::QuitProcessed);
@@ -328,169 +349,10 @@ void Application::closeApplication() {
_localSocket.close();
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
delete _updateReply;
_updateReply = 0;
if (_updateChecker) _updateChecker->deleteLater();
_updateChecker = 0;
if (_updateThread) {
_updateThread->quit();
}
_updateThread = 0;
_updateChecker = nullptr;
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
}
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
void Application::updateCheck() {
startUpdateCheck(false);
}
void Application::updateGotCurrent() {
if (!_updateReply || _updateThread) return;
cSetLastUpdateCheck(unixtime());
QRegularExpressionMatch m = QRegularExpression(qsl("^\\s*(\\d+)\\s*:\\s*([\\x21-\\x7f]+)\\s*$")).match(QString::fromLatin1(_updateReply->readAll()));
if (m.hasMatch()) {
uint64 currentVersion = m.captured(1).toULongLong();
QString url = m.captured(2);
bool betaVersion = false;
if (url.startsWith(qstr("beta_"))) {
betaVersion = true;
url = url.mid(5) + '_' + countBetaVersionSignature(currentVersion);
}
if ((!betaVersion || cBetaVersion()) && currentVersion > (betaVersion ? cBetaVersion() : uint64(AppVersion))) {
_updateThread = new QThread();
connect(_updateThread, SIGNAL(finished()), _updateThread, SLOT(deleteLater()));
_updateChecker = new UpdateChecker(_updateThread, url);
_updateThread->start();
}
}
if (_updateReply) _updateReply->deleteLater();
_updateReply = 0;
if (!_updateThread) {
QDir updates(cWorkingDir() + "tupdates");
if (updates.exists()) {
QFileInfoList list = updates.entryInfoList(QDir::Files);
for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) {
if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) {
QFile(i->absoluteFilePath()).remove();
}
}
}
emit updateLatest();
}
startUpdateCheck(true);
Local::writeSettings();
}
void Application::updateFailedCurrent(QNetworkReply::NetworkError e) {
LOG(("App Error: could not get current version (update check): %1").arg(e));
if (_updateReply) _updateReply->deleteLater();
_updateReply = 0;
emit updateFailed();
startUpdateCheck(true);
}
void Application::onUpdateReady() {
if (_updateChecker) {
_updateChecker->deleteLater();
_updateChecker = nullptr;
}
_updateCheckTimer->stop();
cSetLastUpdateCheck(unixtime());
Local::writeSettings();
}
void Application::onUpdateFailed() {
if (_updateChecker) {
_updateChecker->deleteLater();
_updateChecker = 0;
if (_updateThread) _updateThread->quit();
_updateThread = 0;
}
cSetLastUpdateCheck(unixtime());
Local::writeSettings();
}
Application::UpdatingState Application::updatingState() {
if (!_updateThread) return Application::UpdatingNone;
if (!_updateChecker) return Application::UpdatingReady;
return Application::UpdatingDownload;
}
int32 Application::updatingSize() {
if (!_updateChecker) return 0;
return _updateChecker->size();
}
int32 Application::updatingReady() {
if (!_updateChecker) return 0;
return _updateChecker->ready();
}
void Application::stopUpdate() {
if (_updateReply) {
_updateReply->abort();
_updateReply->deleteLater();
_updateReply = 0;
}
if (_updateChecker) {
_updateChecker->deleteLater();
_updateChecker = 0;
if (_updateThread) _updateThread->quit();
_updateThread = 0;
}
}
void Application::startUpdateCheck(bool forceWait) {
if (!Sandbox::started()) return;
_updateCheckTimer->stop();
if (_updateThread || _updateReply || !cAutoUpdate() || cExeName().isEmpty()) return;
int32 constDelay = cBetaVersion() ? 600 : UpdateDelayConstPart, randDelay = cBetaVersion() ? 300 : UpdateDelayRandPart;
int32 updateInSecs = cLastUpdateCheck() + constDelay + int32(rand() % randDelay) - unixtime();
bool sendRequest = (updateInSecs <= 0 || updateInSecs > (constDelay + randDelay));
if (!sendRequest && !forceWait) {
QDir updates(cWorkingDir() + "tupdates");
if (updates.exists()) {
QFileInfoList list = updates.entryInfoList(QDir::Files);
for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) {
if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) {
sendRequest = true;
}
}
}
}
if (cManyInstance() && !cDebug()) return; // only main instance is updating
if (sendRequest) {
QUrl url(cUpdateURL());
if (cBetaVersion()) {
url.setQuery(qsl("version=%1&beta=%2").arg(AppVersion).arg(cBetaVersion()));
} else if (cAlphaVersion()) {
url.setQuery(qsl("version=%1&alpha=1").arg(AppVersion));
} else {
url.setQuery(qsl("version=%1").arg(AppVersion));
}
QString u = url.toString();
QNetworkRequest checkVersion(url);
if (_updateReply) _updateReply->deleteLater();
App::setProxySettings(_updateManager);
_updateReply = _updateManager.get(checkVersion);
connect(_updateReply, SIGNAL(finished()), this, SLOT(updateGotCurrent()));
connect(_updateReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(updateFailedCurrent(QNetworkReply::NetworkError)));
emit updateChecking();
} else {
_updateCheckTimer->start((updateInSecs + 5) * 1000);
}
}
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
inline Application *application() {
return qobject_cast<Application*>(QApplication::instance());
}
@@ -542,73 +404,6 @@ void adjustSingleTimers() {
base::Timer::Adjust();
}
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
void startUpdateCheck() {
if (auto a = application()) {
return a->startUpdateCheck(false);
}
}
void stopUpdate() {
if (auto a = application()) {
return a->stopUpdate();
}
}
Application::UpdatingState updatingState() {
if (auto a = application()) {
return a->updatingState();
}
return Application::UpdatingNone;
}
int32 updatingSize() {
if (auto a = application()) {
return a->updatingSize();
}
return 0;
}
int32 updatingReady() {
if (auto a = application()) {
return a->updatingReady();
}
return 0;
}
void updateChecking() {
if (auto a = application()) {
emit a->updateChecking();
}
}
void updateLatest() {
if (auto a = application()) {
emit a->updateLatest();
}
}
void updateProgress(qint64 ready, qint64 total) {
if (auto a = application()) {
emit a->updateProgress(ready, total);
}
}
void updateFailed() {
if (auto a = application()) {
emit a->updateFailed();
}
}
void updateReady() {
if (auto a = application()) {
emit a->updateReady();
}
}
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
void connect(const char *signal, QObject *object, const char *method) {
if (auto a = application()) {
a->connect(a, signal, object, method);
@@ -648,4 +443,10 @@ void launch() {
application()->createMessenger();
}
void refreshGlobalProxy() {
if (const auto instance = application()) {
instance->refreshGlobalProxy();
}
}
} // namespace Sandbox

View File

@@ -7,10 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class UpdateChecker;
namespace Core {
class Launcher;
class UpdateChecker;
} // namespace Core
class Application : public QApplication {
@@ -22,6 +21,7 @@ public:
bool event(QEvent *e) override;
void createMessenger();
void refreshGlobalProxy();
~Application();
@@ -58,46 +58,11 @@ private:
void singleInstanceChecked();
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
// Autoupdating
public:
void startUpdateCheck(bool forceWait);
void stopUpdate();
enum UpdatingState {
UpdatingNone,
UpdatingDownload,
UpdatingReady,
};
UpdatingState updatingState();
int32 updatingSize();
int32 updatingReady();
signals:
void updateChecking();
void updateLatest();
void updateProgress(qint64 ready, qint64 total);
void updateReady();
void updateFailed();
public slots:
void updateCheck();
void updateGotCurrent();
void updateFailedCurrent(QNetworkReply::NetworkError e);
void onUpdateReady();
void onUpdateFailed();
private:
object_ptr<SingleTimer> _updateCheckTimer = { nullptr };
QNetworkReply *_updateReply = nullptr;
QNetworkAccessManager _updateManager;
QThread *_updateThread = nullptr;
UpdateChecker *_updateChecker = nullptr;
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
std::unique_ptr<Core::UpdateChecker> _updateChecker;
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
};
namespace Sandbox {
@@ -111,22 +76,7 @@ void execExternal(const QString &cmd);
void adjustSingleTimers();
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
void startUpdateCheck();
void stopUpdate();
Application::UpdatingState updatingState();
int32 updatingSize();
int32 updatingReady();
void updateChecking();
void updateLatest();
void updateProgress(qint64 ready, qint64 total);
void updateFailed();
void updateReady();
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
void refreshGlobalProxy();
void connect(const char *signal, QObject *object, const char *method);

View File

@@ -278,13 +278,18 @@ AuthSession::AuthSession(UserId userId)
, _changelogs(Core::Changelogs::Create(this)) {
Expects(_userId != 0);
_saveDataTimer.setCallback([this] {
_saveDataTimer.setCallback([=] {
Local::writeUserSettings();
});
subscribe(Messenger::Instance().passcodedChanged(), [this] {
subscribe(Messenger::Instance().passcodedChanged(), [=] {
_shouldLockAt = 0;
notifications().updateAll();
});
subscribe(Global::RefConnectionTypeChanged(), [=] {
_api->refreshProxyPromotion();
});
_api->refreshProxyPromotion();
Window::Theme::Background()->start();
}
@@ -304,7 +309,10 @@ base::Observable<void> &AuthSession::downloaderTaskFinished() {
}
bool AuthSession::validateSelf(const MTPUser &user) {
if (user.type() != mtpc_user || !user.c_user().is_self() || user.c_user().vid.v != userId()) {
if (user.type() != mtpc_user || !user.c_user().is_self()) {
LOG(("API Error: bad self user received."));
return false;
} else if (user.c_user().vid.v != userId()) {
LOG(("Auth Error: wrong self user received."));
App::logOutDelayed();
return false;
@@ -314,6 +322,7 @@ bool AuthSession::validateSelf(const MTPUser &user) {
void AuthSession::saveSettingsDelayed(TimeMs delay) {
Expects(this == &Auth());
_saveDataTimer.callOnce(delay);
}

View File

@@ -1,614 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "autoupdater.h"
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#ifdef Q_OS_WIN // use Lzma SDK for win
#include <LzmaLib.h>
#else // Q_OS_WIN
#include <lzma.h>
#endif // else of Q_OS_WIN
#include "application.h"
#include "platform/platform_specific.h"
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
#ifdef Q_OS_WIN
typedef DWORD VerInt;
typedef WCHAR VerChar;
#else // Q_OS_WIN
typedef int VerInt;
typedef wchar_t VerChar;
#endif // Q_OS_WIN
UpdateChecker::UpdateChecker(QThread *thread, const QString &url) : reply(0), already(0), full(0) {
updateUrl = url;
moveToThread(thread);
manager.moveToThread(thread);
App::setProxySettings(manager);
connect(thread, SIGNAL(started()), this, SLOT(start()));
initOutput();
}
void UpdateChecker::initOutput() {
QString fileName;
QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl);
if (m.hasMatch()) {
fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString());
}
if (fileName.isEmpty()) {
fileName = qsl("tupdate-%1").arg(rand_value<uint32>() % 1000000);
}
QString dirStr = cWorkingDir() + qsl("tupdates/");
fileName = dirStr + fileName;
QFileInfo file(fileName);
QDir dir(dirStr);
if (dir.exists()) {
QFileInfoList all = dir.entryInfoList(QDir::Files);
for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) {
if (i->absoluteFilePath() != file.absoluteFilePath()) {
QFile::remove(i->absoluteFilePath());
}
}
} else {
dir.mkdir(dir.absolutePath());
}
outputFile.setFileName(fileName);
if (file.exists()) {
uint64 fullSize = file.size();
if (fullSize < INT_MAX) {
int32 goodSize = (int32)fullSize;
if (goodSize % UpdateChunk) {
goodSize = goodSize - (goodSize % UpdateChunk);
if (goodSize) {
if (outputFile.open(QIODevice::ReadOnly)) {
QByteArray goodData = outputFile.readAll().mid(0, goodSize);
outputFile.close();
if (outputFile.open(QIODevice::WriteOnly)) {
outputFile.write(goodData);
outputFile.close();
QMutexLocker lock(&mutex);
already = goodSize;
}
}
}
} else {
QMutexLocker lock(&mutex);
already = goodSize;
}
}
if (!already) {
QFile::remove(fileName);
}
}
}
void UpdateChecker::start() {
sendRequest();
}
void UpdateChecker::sendRequest() {
QNetworkRequest req(updateUrl);
QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";
req.setRawHeader("Range", rangeHeaderValue);
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
if (reply) reply->deleteLater();
reply = manager.get(req);
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64)));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError)));
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot()));
}
void UpdateChecker::partMetaGot() {
typedef QList<QNetworkReply::RawHeaderPair> Pairs;
Pairs pairs = reply->rawHeaderPairs();
for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) {
if (QString::fromUtf8(i->first).toLower() == "content-range") {
QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second));
if (m.hasMatch()) {
{
QMutexLocker lock(&mutex);
full = m.captured(1).toInt();
}
Sandbox::updateProgress(already, full);
}
}
}
}
int32 UpdateChecker::ready() {
QMutexLocker lock(&mutex);
return already;
}
int32 UpdateChecker::size() {
QMutexLocker lock(&mutex);
return full;
}
void UpdateChecker::partFinished(qint64 got, qint64 total) {
if (!reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (statusCode.isValid()) {
int status = statusCode.toInt();
if (status != 200 && status != 206 && status != 416) {
LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status));
return fatalFail();
}
}
if (!already && !full) {
QMutexLocker lock(&mutex);
full = total;
}
DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total));
if (!outputFile.isOpen()) {
if (!outputFile.open(QIODevice::Append)) {
LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName()));
return fatalFail();
}
}
QByteArray r = reply->readAll();
if (!r.isEmpty()) {
outputFile.write(r);
QMutexLocker lock(&mutex);
already += r.size();
}
if (got >= total) {
reply->deleteLater();
reply = 0;
outputFile.close();
unpackUpdate();
} else {
Sandbox::updateProgress(already, full);
}
}
void UpdateChecker::partFailed(QNetworkReply::NetworkError e) {
if (!reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
reply->deleteLater();
reply = 0;
if (statusCode.isValid()) {
int status = statusCode.toInt();
if (status == 416) { // Requested range not satisfiable
outputFile.close();
unpackUpdate();
return;
}
}
LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e));
Sandbox::updateFailed();
}
void UpdateChecker::fatalFail() {
clearAll();
Sandbox::updateFailed();
}
void UpdateChecker::clearAll() {
psDeleteDir(cWorkingDir() + qsl("tupdates"));
}
//QString winapiErrorWrap() {
// WCHAR errMsg[2048];
// DWORD errorCode = GetLastError();
// LPTSTR errorText = NULL, errorTextDefault = L"(Unknown error)";
// FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorText, 0, 0);
// if (!errorText) {
// errorText = errorTextDefault;
// }
// StringCbPrintf(errMsg, sizeof(errMsg), L"Error code: %d, error message: %s", errorCode, errorText);
// if (errorText != errorTextDefault) {
// LocalFree(errorText);
// }
// return QString::fromWCharArray(errMsg);
//}
void UpdateChecker::unpackUpdate() {
QByteArray packed;
if (!outputFile.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read updates file!"));
return fatalFail();
}
#ifdef Q_OS_WIN // use Lzma SDK for win
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
#else // Q_OS_WIN
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header
#endif // Q_OS_WIN
QByteArray compressed = outputFile.readAll();
int32 compressedLen = compressed.size() - hSize;
if (compressedLen <= 0) {
LOG(("Update Error: bad compressed size: %1").arg(compressed.size()));
return fatalFail();
}
outputFile.close();
QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready");
psDeleteDir(tempDirPath);
QDir tempDir(tempDirPath);
if (tempDir.exists() || QFile(readyFilePath).exists()) {
LOG(("Update Error: cant clear tupdates/temp dir!"));
return fatalFail();
}
uchar sha1Buffer[20];
bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen);
if (!goodSha1) {
LOG(("Update Error: bad SHA1 hash of update file!"));
return fatalFail();
}
RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>(AppAlphaVersion ? UpdatesPublicAlphaKey : UpdatesPublicKey), -1), 0, 0, 0);
if (!pbKey) {
LOG(("Update Error: cant read public rsa key!"));
return fatalFail();
}
if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature
RSA_free(pbKey);
if (cAlphaVersion() || cBetaVersion()) { // try other public key, if we are in alpha or beta version
pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>(AppAlphaVersion ? UpdatesPublicKey : UpdatesPublicAlphaKey), -1), 0, 0, 0);
if (!pbKey) {
LOG(("Update Error: cant read public rsa key!"));
return fatalFail();
}
if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature
RSA_free(pbKey);
LOG(("Update Error: bad RSA signature of update file!"));
return fatalFail();
}
} else {
LOG(("Update Error: bad RSA signature of update file!"));
return fatalFail();
}
}
RSA_free(pbKey);
QByteArray uncompressed;
int32 uncompressedLen;
memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen);
uncompressed.resize(uncompressedLen);
size_t resultLen = uncompressed.size();
#ifdef Q_OS_WIN // use Lzma SDK for win
SizeT srcLen = compressedLen;
int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
if (uncompressRes != SZ_OK) {
LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes));
return fatalFail();
}
#else // Q_OS_WIN
lzma_stream stream = LZMA_STREAM_INIT;
lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED);
if (ret != LZMA_OK) {
const char *msg;
switch (ret) {
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break;
case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break;
default: msg = "Unknown error, possibly a bug"; break;
}
LOG(("Error initializing the decoder: %1 (error code %2)").arg(msg).arg(ret));
return fatalFail();
}
stream.avail_in = compressedLen;
stream.next_in = (uint8_t*)(compressed.constData() + hSize);
stream.avail_out = resultLen;
stream.next_out = (uint8_t*)uncompressed.data();
lzma_ret res = lzma_code(&stream, LZMA_FINISH);
if (stream.avail_in) {
LOG(("Error in decompression, %1 bytes left in _in of %2 whole.").arg(stream.avail_in).arg(compressedLen));
return fatalFail();
} else if (stream.avail_out) {
LOG(("Error in decompression, %1 bytes free left in _out of %2 whole.").arg(stream.avail_out).arg(resultLen));
return fatalFail();
}
lzma_end(&stream);
if (res != LZMA_OK && res != LZMA_STREAM_END) {
const char *msg;
switch (res) {
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break;
case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break;
case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break;
case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break;
default: msg = "Unknown error, possibly a bug"; break;
}
LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res));
return fatalFail();
}
#endif // Q_OS_WIN
tempDir.mkdir(tempDir.absolutePath());
quint32 version;
{
QDataStream stream(uncompressed);
stream.setVersion(QDataStream::Qt_5_1);
stream >> version;
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
quint64 betaVersion = 0;
if (version == 0x7FFFFFFF) { // beta version
stream >> betaVersion;
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read beta version from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (!cBetaVersion() || betaVersion <= cBetaVersion()) {
LOG(("Update Error: downloaded beta version %1 is not greater, than mine %2").arg(betaVersion).arg(cBetaVersion()));
return fatalFail();
}
} else if (int32(version) <= AppVersion) {
LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion));
return fatalFail();
}
quint32 filesCount;
stream >> filesCount;
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (!filesCount) {
LOG(("Update Error: update is empty!"));
return fatalFail();
}
for (uint32 i = 0; i < filesCount; ++i) {
QString relativeName;
quint32 fileSize;
QByteArray fileInnerData;
bool executable = false;
stream >> relativeName >> fileSize >> fileInnerData;
#if defined Q_OS_MAC || defined Q_OS_LINUX
stream >> executable;
#endif // Q_OS_MAC || Q_OS_LINUX
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (fileSize != quint32(fileInnerData.size())) {
LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size()));
return fatalFail();
}
QFile f(tempDirPath + '/' + relativeName);
if (!QDir().mkpath(QFileInfo(f).absolutePath())) {
LOG(("Update Error: cant mkpath for file '%1'").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
if (!f.open(QIODevice::WriteOnly)) {
LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
auto writtenBytes = f.write(fileInnerData);
if (writtenBytes != fileSize) {
f.close();
LOG(("Update Error: cant write file '%1', desiredSize: %2, write result: %3").arg(tempDirPath + '/' + relativeName).arg(fileSize).arg(writtenBytes));
return fatalFail();
}
f.close();
if (executable) {
QFileDevice::Permissions p = f.permissions();
p |= QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther;
f.setPermissions(p);
}
}
// create tdata/version file
tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath());
std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString();
VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar));
VerChar versionStr[32];
memcpy(versionStr, versionString.c_str(), versionLen);
QFile fVersion(tempDirPath + qsl("/tdata/version"));
if (!fVersion.open(QIODevice::WriteOnly)) {
LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version")));
return fatalFail();
}
fVersion.write((const char*)&versionNum, sizeof(VerInt));
if (versionNum == 0x7FFFFFFF) { // beta version
fVersion.write((const char*)&betaVersion, sizeof(quint64));
} else {
fVersion.write((const char*)&versionLen, sizeof(VerInt));
fVersion.write((const char*)&versionStr[0], versionLen);
}
fVersion.close();
}
QFile readyFile(readyFilePath);
if (readyFile.open(QIODevice::WriteOnly)) {
if (readyFile.write("1", 1)) {
readyFile.close();
} else {
LOG(("Update Error: cant write ready file '%1'").arg(readyFilePath));
return fatalFail();
}
} else {
LOG(("Update Error: cant create ready file '%1'").arg(readyFilePath));
return fatalFail();
}
outputFile.remove();
Sandbox::updateReady();
}
UpdateChecker::~UpdateChecker() {
delete reply;
reply = 0;
}
bool checkReadyUpdate() {
QString readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"), readyPath = cWorkingDir() + qsl("tupdates/temp");
if (!QFile(readyFilePath).exists() || cExeName().isEmpty()) {
if (QDir(cWorkingDir() + qsl("tupdates/ready")).exists() || QDir(cWorkingDir() + qsl("tupdates/temp")).exists()) {
UpdateChecker::clearAll();
}
return false;
}
// check ready version
QString versionPath = readyPath + qsl("/tdata/version");
{
QFile fVersion(versionPath);
if (!fVersion.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read version file '%1'").arg(versionPath));
UpdateChecker::clearAll();
return false;
}
VerInt versionNum;
if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) {
LOG(("Update Error: cant read version from file '%1'").arg(versionPath));
UpdateChecker::clearAll();
return false;
}
if (versionNum == 0x7FFFFFFF) { // beta version
quint64 betaVersion = 0;
if (fVersion.read((char*)&betaVersion, sizeof(quint64)) != sizeof(quint64)) {
LOG(("Update Error: cant read beta version from file '%1'").arg(versionPath));
UpdateChecker::clearAll();
return false;
}
if (!cBetaVersion() || betaVersion <= cBetaVersion()) {
LOG(("Update Error: cant install beta version %1 having beta version %2").arg(betaVersion).arg(cBetaVersion()));
UpdateChecker::clearAll();
return false;
}
} else if (versionNum <= AppVersion) {
LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion));
UpdateChecker::clearAll();
return false;
}
fVersion.close();
}
#ifdef Q_OS_WIN
QString curUpdater = (cExeDir() + qsl("Updater.exe"));
QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Updater.exe"));
#elif defined Q_OS_MAC // Q_OS_WIN
QString curUpdater = (cExeDir() + cExeName() + qsl("/Contents/Frameworks/Updater"));
QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Telegram.app/Contents/Frameworks/Updater"));
#elif defined Q_OS_LINUX // Q_OS_MAC
QString curUpdater = (cExeDir() + qsl("Updater"));
QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Updater"));
#endif // Q_OS_LINUX
if (!updater.exists()) {
QFileInfo current(curUpdater);
if (!current.exists()) {
UpdateChecker::clearAll();
return false;
}
if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) {
UpdateChecker::clearAll();
return false;
}
}
#ifdef Q_OS_WIN
if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) {
DWORD errorCode = GetLastError();
if (errorCode == ERROR_ACCESS_DENIED) { // we are in write-protected dir, like Program Files
cSetWriteProtected(true);
return true;
} else {
UpdateChecker::clearAll();
return false;
}
}
if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) {
UpdateChecker::clearAll();
return false;
}
#elif defined Q_OS_MAC // Q_OS_WIN
QDir().mkpath(QFileInfo(curUpdater).absolutePath());
DEBUG_LOG(("Update Info: moving %1 to %2...").arg(updater.absoluteFilePath()).arg(curUpdater));
if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) {
UpdateChecker::clearAll();
return false;
}
#elif defined Q_OS_LINUX // Q_OS_MAC
if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) {
UpdateChecker::clearAll();
return false;
}
#endif // Q_OS_LINUX
return true;
}
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
QString countBetaVersionSignature(uint64 version) { // duplicated in packer.cpp
if (cBetaPrivateKey().isEmpty()) {
LOG(("Error: Trying to count beta version signature without beta private key!"));
return QString();
}
QByteArray signedData = (qstr("TelegramBeta_") + QString::number(version, 16).toLower()).toUtf8();
static const int32 shaSize = 20, keySize = 128;
uchar sha1Buffer[shaSize];
hashSha1(signedData.constData(), signedData.size(), sha1Buffer); // count sha1
uint32 siglen = 0;
RSA *prKey = PEM_read_bio_RSAPrivateKey(BIO_new_mem_buf(const_cast<char*>(cBetaPrivateKey().constData()), -1), 0, 0, 0);
if (!prKey) {
LOG(("Error: Could not read beta private key!"));
return QString();
}
if (RSA_size(prKey) != keySize) {
LOG(("Error: Bad beta private key size: %1").arg(RSA_size(prKey)));
RSA_free(prKey);
return QString();
}
QByteArray signature;
signature.resize(keySize);
if (RSA_sign(NID_sha1, (const uchar*)(sha1Buffer), shaSize, (uchar*)(signature.data()), &siglen, prKey) != 1) { // count signature
LOG(("Error: Counting beta version signature failed!"));
RSA_free(prKey);
return QString();
}
RSA_free(prKey);
if (siglen != keySize) {
LOG(("Error: Bad beta version signature length: %1").arg(siglen));
return QString();
}
signature = signature.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
signature = signature.replace('-', '8').replace('_', 'B');
return QString::fromUtf8(signature.mid(19, 32));
}

View File

@@ -1,63 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
#include <QtNetwork/QLocalSocket>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QNetworkReply>
class UpdateChecker : public QObject {
Q_OBJECT
public:
UpdateChecker(QThread *thread, const QString &url);
void unpackUpdate();
int32 ready();
int32 size();
static void clearAll();
~UpdateChecker();
public slots:
void start();
void partMetaGot();
void partFinished(qint64 got, qint64 total);
void partFailed(QNetworkReply::NetworkError e);
void sendRequest();
private:
void initOutput();
void fatalFail();
QString updateUrl;
QNetworkAccessManager manager;
QNetworkReply *reply;
int32 already, full;
QFile outputFile;
QMutex mutex;
};
bool checkReadyUpdate();
#else // TDESKTOP_DISABLE_AUTOUPDATE
class UpdateChecker : public QObject {
Q_OBJECT
};
#endif // TDESKTOP_DISABLE_AUTOUPDATE
QString countBetaVersionSignature(uint64 version);

View File

@@ -47,6 +47,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#define ARCH_CPU_X86_FAMILY 1
#define ARCH_CPU_X86 1
#define ARCH_CPU_32_BITS 1
#elif defined(__aarch64__)
#define ARCH_CPU_64_BITS 1
#elif defined(_M_ARM) || defined(__arm__)
#define ARCH_CPU_32_BITS 1
#else
#error Please add support for your architecture in base/build_config.h
#endif

View File

@@ -0,0 +1,137 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <gsl/gsl_byte>
namespace bytes {
using span = gsl::span<gsl::byte>;
using const_span = gsl::span<const gsl::byte>;
using vector = std::vector<gsl::byte>;
template <
typename Container,
typename = std::enable_if_t<!std::is_const_v<Container>>>
inline span make_span(Container &container) {
return gsl::as_writeable_bytes(gsl::make_span(container));
}
template <typename Container>
inline const_span make_span(const Container &container) {
return gsl::as_bytes(gsl::make_span(container));
}
template <typename Type, std::ptrdiff_t Extent>
inline span make_span(gsl::span<Type, Extent> container) {
return gsl::as_writeable_bytes(container);
}
template <typename Type, std::ptrdiff_t Extent>
inline const_span make_span(gsl::span<const Type, Extent> container) {
return gsl::as_bytes(container);
}
template <typename Type>
inline span make_span(Type *value, std::size_t count) {
return gsl::as_writeable_bytes(gsl::make_span(value, count));
}
template <typename Type>
inline const_span make_span(const Type *value, std::size_t count) {
return gsl::as_bytes(gsl::make_span(value, count));
}
template <typename Container>
inline vector make_vector(const Container &container) {
const auto buffer = bytes::make_span(container);
return { buffer.begin(), buffer.end() };
}
inline void copy(span destination, const_span source) {
Expects(destination.size() >= source.size());
memcpy(destination.data(), source.data(), source.size());
}
inline void move(span destination, const_span source) {
Expects(destination.size() >= source.size());
memmove(destination.data(), source.data(), source.size());
}
inline void set_with_const(span destination, gsl::byte value) {
memset(
destination.data(),
gsl::to_integer<unsigned char>(value),
destination.size());
}
inline int compare(const_span a, const_span b) {
const auto aSize = a.size(), bSize = b.size();
return (aSize > bSize)
? 1
: (aSize < bSize)
? -1
: memcmp(a.data(), b.data(), aSize);
}
namespace details {
template <typename Arg>
std::size_t spansLength(Arg &&arg) {
return bytes::make_span(arg).size();
}
template <typename Arg, typename ...Args>
std::size_t spansLength(Arg &&arg, Args &&...args) {
return bytes::make_span(arg).size() + spansLength(args...);
}
template <typename Arg>
void spansAppend(span destination, Arg &&arg) {
bytes::copy(destination, bytes::make_span(arg));
}
template <typename Arg, typename ...Args>
void spansAppend(span destination, Arg &&arg, Args &&...args) {
const auto data = bytes::make_span(arg);
bytes::copy(destination, data);
spansAppend(destination.subspan(data.size()), args...);
}
} // namespace details
template <
typename ...Args,
typename = std::enable_if_t<(sizeof...(Args) > 1)>>
vector concatenate(Args &&...args) {
const auto size = details::spansLength(args...);
auto result = vector(size);
details::spansAppend(make_span(result), args...);
return result;
}
template <
typename SpanRange>
vector concatenate(SpanRange args) {
auto size = std::size_t(0);
for (const auto &arg : args) {
size += bytes::make_span(arg).size();
}
auto result = vector(size);
auto buffer = make_span(result);
for (const auto &arg : args) {
const auto part = bytes::make_span(arg);
bytes::copy(buffer, part);
buffer = buffer.subspan(part.size());
}
return result;
}
} // namespace bytes

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <openssl/bn.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#include "base/bytes.h"
namespace openssl {
@@ -223,3 +224,13 @@ inline int FillRandom(base::byte_span bytes) {
}
} // namespace openssl
namespace bytes {
inline void set_random(span destination) {
RAND_bytes(
reinterpret_cast<unsigned char*>(destination.data()),
destination.size());
}
} // namespace bytes

View File

@@ -38,4 +38,10 @@ QMap<QString, QString> url_parse_params(
return result;
}
bool is_ipv6(const QString &ip) {
//static const auto regexp = QRegularExpression("^[a-fA-F0-9:]+$");
//return regexp.match(ip).hasMatch();
return ip.indexOf('.') < 0 && ip.indexOf(':') >= 0;
}
} // namespace qthelp

View File

@@ -24,4 +24,6 @@ enum class UrlParamNameTransform {
// Parses a string like "p1=v1&p2=v2&..&pn=vn" to a map.
QMap<QString, QString> url_parse_params(const QString &params, UrlParamNameTransform transform = UrlParamNameTransform::NoTransform);
bool is_ipv6(const QString &ip);
} // namespace qthelp

View File

@@ -17,12 +17,26 @@ QObject *TimersAdjuster() {
} // namespace
Timer::Timer(base::lambda<void()> callback) : QObject(nullptr)
Timer::Timer(
not_null<QThread*> thread,
base::lambda<void()> callback)
: Timer(std::move(callback)) {
moveToThread(thread);
}
Timer::Timer(base::lambda<void()> callback)
: QObject(nullptr)
, _callback(std::move(callback))
, _type(Qt::PreciseTimer)
, _adjusted(false) {
setRepeat(Repeat::Interval);
connect(TimersAdjuster(), &QObject::destroyed, this, [this] { adjust(); }, Qt::QueuedConnection);
connect(
TimersAdjuster(),
&QObject::destroyed,
this,
[this] { adjust(); },
Qt::QueuedConnection);
}
void Timer::start(TimeMs timeout, Qt::TimerType type, Repeat repeat) {
@@ -56,7 +70,11 @@ TimeMs Timer::remainingTime() const {
void Timer::Adjust() {
QObject emitter;
connect(&emitter, &QObject::destroyed, TimersAdjuster(), &QObject::destroyed);
connect(
&emitter,
&QObject::destroyed,
TimersAdjuster(),
&QObject::destroyed);
}
void Timer::adjust() {
@@ -70,6 +88,7 @@ void Timer::adjust() {
void Timer::setTimeout(TimeMs timeout) {
Expects(timeout >= 0 && timeout <= std::numeric_limits<int>::max());
_timeout = static_cast<unsigned int>(timeout);
}
@@ -93,8 +112,12 @@ void Timer::timerEvent(QTimerEvent *e) {
}
}
int DelayedCallTimer::call(TimeMs timeout, lambda_once<void()> callback, Qt::TimerType type) {
int DelayedCallTimer::call(
TimeMs timeout,
lambda_once<void()> callback,
Qt::TimerType type) {
Expects(timeout >= 0);
if (!callback) {
return 0;
}
@@ -108,7 +131,7 @@ int DelayedCallTimer::call(TimeMs timeout, lambda_once<void()> callback, Qt::Tim
void DelayedCallTimer::cancel(int callId) {
if (callId) {
killTimer(callId);
_callbacks.erase(callId);
_callbacks.remove(callId);
}
}

View File

@@ -14,7 +14,10 @@ namespace base {
class Timer final : private QObject {
public:
Timer(base::lambda<void()> callback = base::lambda<void()>());
explicit Timer(
not_null<QThread*> thread,
base::lambda<void()> callback = nullptr);
explicit Timer(base::lambda<void()> callback = nullptr);
static Qt::TimerType DefaultType(TimeMs timeout) {
constexpr auto kThreshold = TimeMs(1000);
@@ -85,17 +88,23 @@ private:
class DelayedCallTimer final : private QObject {
public:
int call(TimeMs timeout, lambda_once<void()> callback) {
return call(timeout, std::move(callback), Timer::DefaultType(timeout));
return call(
timeout,
std::move(callback),
Timer::DefaultType(timeout));
}
int call(TimeMs timeout, lambda_once<void()> callback, Qt::TimerType type);
int call(
TimeMs timeout,
lambda_once<void()> callback,
Qt::TimerType type);
void cancel(int callId);
protected:
void timerEvent(QTimerEvent *e) override;
private:
std::map<int, lambda_once<void()>> _callbacks; // Better to use flatmap.
base::flat_map<int, lambda_once<void()>> _callbacks;
};

View File

@@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "autoupdater.h"
#include "boxes/confirm_box.h"
#include "application.h"
#include "ui/widgets/buttons.h"
@@ -18,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h"
#include "platform/platform_file_utilities.h"
#include "core/click_handler_types.h"
#include "core/update_checker.h"
AboutBox::AboutBox(QWidget *parent)
: _version(this, lng_about_version(lt_version, QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString())), st::aboutVersionLink)
@@ -68,7 +68,7 @@ void AboutBox::showVersionHistory() {
case dbipLinux32: url += qsl("linux32/%1.tar.xz"); break;
case dbipLinux64: url += qsl("linux/%1.tar.xz"); break;
}
url = url.arg(qsl("tbeta%1_%2").arg(cRealBetaVersion()).arg(countBetaVersionSignature(cRealBetaVersion())));
url = url.arg(qsl("tbeta%1_%2").arg(cRealBetaVersion()).arg(Core::countBetaVersionSignature(cRealBetaVersion())));
Application::clipboard()->setText(url);

View File

@@ -19,12 +19,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwidget.h"
#include "mainwindow.h"
QPointer<Ui::RoundButton> BoxContent::addButton(base::lambda<QString()> textFactory, base::lambda<void()> clickCallback) {
return addButton(std::move(textFactory), std::move(clickCallback), st::defaultBoxButton);
QPointer<Ui::RoundButton> BoxContent::addButton(
base::lambda<QString()> textFactory,
base::lambda<void()> clickCallback) {
return addButton(
std::move(textFactory),
std::move(clickCallback),
st::defaultBoxButton);
}
QPointer<Ui::RoundButton> BoxContent::addLeftButton(base::lambda<QString()> textFactory, base::lambda<void()> clickCallback) {
return getDelegate()->addLeftButton(std::move(textFactory), std::move(clickCallback), st::defaultBoxButton);
QPointer<Ui::RoundButton> BoxContent::addLeftButton(
base::lambda<QString()> textFactory,
base::lambda<void()> clickCallback) {
return getDelegate()->addLeftButton(
std::move(textFactory),
std::move(clickCallback),
st::defaultBoxButton);
}
void BoxContent::setInner(object_ptr<TWidget> inner) {
@@ -117,7 +127,7 @@ void BoxContent::updateShadowsVisibility() {
(top > 0 || _innerTopSkip > 0),
anim::type::normal);
_bottomShadow->toggle(
(top < _scroll->scrollTopMax()),
(top < _scroll->scrollTopMax() || _innerBottomSkip > 0),
anim::type::normal);
}
@@ -145,6 +155,16 @@ void BoxContent::setInnerTopSkip(int innerTopSkip, bool scrollBottomFixed) {
}
}
void BoxContent::setInnerBottomSkip(int innerBottomSkip) {
if (_innerBottomSkip != innerBottomSkip) {
auto delta = innerBottomSkip - _innerBottomSkip;
_innerBottomSkip = innerBottomSkip;
if (_scroll && width() > 0) {
updateScrollAreaGeometry();
}
}
}
void BoxContent::setInnerVisible(bool scrollAreaVisible) {
if (_scroll) {
_scroll->setVisible(scrollAreaVisible);
@@ -169,13 +189,15 @@ void BoxContent::resizeEvent(QResizeEvent *e) {
}
void BoxContent::updateScrollAreaGeometry() {
auto newScrollHeight = height() - _innerTopSkip;
auto newScrollHeight = height() - _innerTopSkip - _innerBottomSkip;
auto changed = (_scroll->height() != newScrollHeight);
_scroll->setGeometryToLeft(0, _innerTopSkip, width(), newScrollHeight);
_topShadow->entity()->resize(width(), st::lineWidth);
_topShadow->moveToLeft(0, _innerTopSkip);
_bottomShadow->entity()->resize(width(), st::lineWidth);
_bottomShadow->moveToLeft(0, height() - st::lineWidth);
_bottomShadow->moveToLeft(
0,
height() - _innerBottomSkip - st::lineWidth);
if (changed) {
updateInnerVisibleTopBottom();
@@ -184,7 +206,7 @@ void BoxContent::updateScrollAreaGeometry() {
(top > 0 || _innerTopSkip > 0),
anim::type::instant);
_bottomShadow->toggle(
(top < _scroll->scrollTopMax()),
(top < _scroll->scrollTopMax() || _innerBottomSkip > 0),
anim::type::instant);
}
}

View File

@@ -130,19 +130,29 @@ protected:
getDelegate()->setDimensions(newWidth, maxHeight);
}
void setInnerTopSkip(int topSkip, bool scrollBottomFixed = false);
void setInnerBottomSkip(int bottomSkip);
template <typename Widget>
QPointer<Widget> setInnerWidget(object_ptr<Widget> inner, const style::ScrollArea &st, int topSkip = 0) {
QPointer<Widget> setInnerWidget(
object_ptr<Widget> inner,
const style::ScrollArea &st,
int topSkip = 0,
int bottomSkip = 0) {
auto result = QPointer<Widget>(inner.data());
setInnerTopSkip(topSkip);
setInnerBottomSkip(bottomSkip);
setInner(std::move(inner), st);
return result;
}
template <typename Widget>
QPointer<Widget> setInnerWidget(object_ptr<Widget> inner, int topSkip = 0) {
QPointer<Widget> setInnerWidget(
object_ptr<Widget> inner,
int topSkip = 0,
int bottomSkip = 0) {
auto result = QPointer<Widget>(inner.data());
setInnerTopSkip(topSkip);
setInnerBottomSkip(bottomSkip);
setInner(std::move(inner));
return result;
}
@@ -183,6 +193,7 @@ private:
bool _preparing = false;
bool _noContentMargin = false;
int _innerTopSkip = 0;
int _innerBottomSkip = 0;
object_ptr<Ui::ScrollArea> _scroll = { nullptr };
object_ptr<Ui::FadeShadow> _topShadow = { nullptr };
object_ptr<Ui::FadeShadow> _bottomShadow = { nullptr };

View File

@@ -32,8 +32,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kMaxGroupChannelTitle = 255;
constexpr auto kMaxChannelDescription = 255;
constexpr auto kMaxGroupChannelTitle = 255; // See also edit_peer_info_box.
constexpr auto kMaxChannelDescription = 255; // See also edit_peer_info_box.
constexpr auto kMaxBioLength = 70;
constexpr auto kMinUsernameLength = 5;
@@ -122,9 +122,9 @@ void AddContactBox::prepare() {
}
updateButtons();
connect(_first, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(_last, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(_phone, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(_first, &Ui::InputField::submitted, [=] { submit(); });
connect(_last, &Ui::InputField::submitted, [=] { submit(); });
connect(_phone, &Ui::PhoneInput::submitted, [=] { submit(); });
setDimensions(st::boxWideWidth, st::contactPadding.top() + _first->height() + st::contactSkip + _last->height() + st::contactPhoneSkip + _phone->height() + st::contactPadding.bottom() + st::boxPadding.bottom());
}
@@ -170,21 +170,21 @@ void AddContactBox::resizeEvent(QResizeEvent *e) {
}
}
void AddContactBox::onSubmit() {
void AddContactBox::submit() {
if (_first->hasFocus()) {
_last->setFocus();
} else if (_last->hasFocus()) {
if (_phone->isEnabled()) {
_phone->setFocus();
} else {
onSave();
save();
}
} else if (_phone->hasFocus()) {
onSave();
save();
}
}
void AddContactBox::onSave() {
void AddContactBox::save() {
if (_addRequest) return;
auto firstName = TextUtilities::PrepareForSending(_first->getLastText());
@@ -274,7 +274,7 @@ void AddContactBox::onSaveUserDone(const MTPcontacts_ImportedContacts &res) {
closeBox();
}
void AddContactBox::onRetry() {
void AddContactBox::retry() {
_addRequest = 0;
_contactId = 0;
showChildren();
@@ -291,9 +291,9 @@ void AddContactBox::onRetry() {
void AddContactBox::updateButtons() {
clearButtons();
if (_retrying) {
addButton(langFactory(lng_try_other_contact), [this] { onRetry(); });
addButton(langFactory(lng_try_other_contact), [this] { retry(); });
} else {
addButton(langFactory(_user ? lng_settings_save : lng_add_contact), [this] { onSave(); });
addButton(langFactory(_user ? lng_settings_save : lng_add_contact), [this] { save(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
}
}
@@ -320,20 +320,29 @@ void GroupInfoBox::prepare() {
? lng_dlg_new_channel_name
: lng_dlg_new_group_name));
_title->setMaxLength(kMaxGroupChannelTitle);
_title->setInstantReplaces(Ui::InstantReplaces::Default());
_title ->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
if (_creating == CreatingGroupChannel) {
_description.create(this, st::newGroupDescription, langFactory(lng_create_group_description));
_description.create(
this,
st::newGroupDescription,
Ui::InputField::Mode::MultiLine,
langFactory(lng_create_group_description));
_description->show();
_description->setMaxLength(kMaxChannelDescription);
_description->setInstantReplaces(Ui::InstantReplaces::Default());
_description->setInstantReplacesEnabled(
Global::ReplaceEmojiValue());
connect(_description, SIGNAL(resized()), this, SLOT(onDescriptionResized()));
connect(_description, SIGNAL(submitted(bool)), this, SLOT(onNext()));
connect(_description, SIGNAL(cancelled()), this, SLOT(onClose()));
connect(_description, &Ui::InputField::resized, [=] { descriptionResized(); });
connect(_description, &Ui::InputField::submitted, [=] { submit(); });
connect(_description, &Ui::InputField::cancelled, [=] { closeBox(); });
}
connect(_title, SIGNAL(submitted(bool)), this, SLOT(onNameSubmit()));
connect(_title, &Ui::InputField::submitted, [=] { submitName(); });
addButton(langFactory(_creating == CreatingGroupChannel ? lng_create_group_create : lng_create_group_next), [this] { onNext(); });
addButton(langFactory(_creating == CreatingGroupChannel ? lng_create_group_create : lng_create_group_next), [this] { submit(); });
addButton(langFactory(_fromTypeChoose ? lng_create_group_back : lng_cancel), [this] { closeBox(); });
updateMaxHeight();
@@ -364,14 +373,14 @@ void GroupInfoBox::resizeEvent(QResizeEvent *e) {
}
}
void GroupInfoBox::onNameSubmit() {
void GroupInfoBox::submitName() {
if (_title->getLastText().trimmed().isEmpty()) {
_title->setFocus();
_title->showError();
} else if (_description) {
_description->setFocus();
} else {
onNext();
submit();
}
}
@@ -451,11 +460,15 @@ void GroupInfoBox::createGroup(not_null<PeerListBox*> selectUsersBox, const QStr
}).send();
}
void GroupInfoBox::onNext() {
void GroupInfoBox::submit() {
if (_creationRequestId) return;
auto title = TextUtilities::PrepareForSending(_title->getLastText());
auto description = _description ? TextUtilities::PrepareForSending(_description->getLastText(), TextUtilities::PrepareTextOption::CheckLinks) : QString();
auto description = _description
? TextUtilities::PrepareForSending(
_description->getLastText(),
TextUtilities::PrepareTextOption::CheckLinks)
: QString();
if (title.isEmpty()) {
_title->setFocus();
_title->showError();
@@ -551,7 +564,7 @@ void GroupInfoBox::createChannel(const QString &title, const QString &descriptio
}).send();
}
void GroupInfoBox::onDescriptionResized() {
void GroupInfoBox::descriptionResized() {
updateMaxHeight();
update();
}
@@ -589,14 +602,14 @@ void SetupChannelBox::prepare() {
_checkRequestId = MTP::send(MTPchannels_CheckUsername(_channel->inputChannel, MTP_string("preston")), RPCDoneHandlerPtr(), rpcFail(&SetupChannelBox::onFirstCheckFail));
addButton(langFactory(lng_settings_save), [this] { onSave(); });
addButton(langFactory(_existing ? lng_cancel : lng_create_group_skip), [this] { closeBox(); });
addButton(langFactory(lng_settings_save), [=] { save(); });
addButton(langFactory(_existing ? lng_cancel : lng_create_group_skip), [=] { closeBox(); });
connect(_link, SIGNAL(changed()), this, SLOT(onChange()));
connect(_link, &Ui::MaskedInputField::changed, [=] { handleChange(); });
_link->setVisible(_privacyGroup->value() == Privacy::Public);
_checkTimer.setSingleShot(true);
connect(&_checkTimer, SIGNAL(timeout()), this, SLOT(onCheck()));
connect(&_checkTimer, &QTimer::timeout, [=] { check(); });
_privacyGroup->setChangedCallback([this](Privacy value) { privacyChanged(value); });
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::InviteLinkChanged, [this](const Notify::PeerUpdate &update) {
@@ -635,7 +648,7 @@ void SetupChannelBox::keyPressEvent(QKeyEvent *e) {
_link->setFocus();
_link->showError();
} else {
onSave();
save();
}
}
} else {
@@ -724,7 +737,7 @@ void SetupChannelBox::updateSelected(const QPoint &cursorGlobalPosition) {
}
}
void SetupChannelBox::onSave() {
void SetupChannelBox::save() {
if (_privacyGroup->value() == Privacy::Private) {
if (_existing) {
_sentUsername = QString();
@@ -747,7 +760,7 @@ void SetupChannelBox::onSave() {
_saveRequestId = MTP::send(MTPchannels_UpdateUsername(_channel->inputChannel, MTP_string(_sentUsername)), rpcDone(&SetupChannelBox::onUpdateDone), rpcFail(&SetupChannelBox::onUpdateFail));
}
void SetupChannelBox::onChange() {
void SetupChannelBox::handleChange() {
QString name = _link->text().trimmed();
if (name.isEmpty()) {
if (!_errorText.isEmpty() || !_goodText.isEmpty()) {
@@ -784,7 +797,7 @@ void SetupChannelBox::onChange() {
}
}
void SetupChannelBox::onCheck() {
void SetupChannelBox::check() {
if (_checkRequestId) {
MTP::cancel(_checkRequestId);
}
@@ -807,7 +820,7 @@ void SetupChannelBox::privacyChanged(Privacy value) {
Ui::show(Box<RevokePublicLinkBox>(base::lambda_guarded(this, [this] {
_tooMuchUsernames = false;
_privacyGroup->setValue(Privacy::Public);
onCheck();
check();
})), LayerOption::KeepOther);
return;
}
@@ -943,16 +956,16 @@ void EditNameBox::prepare() {
newHeight += st::boxPadding.bottom() + st::contactPadding.bottom();
setDimensions(st::boxWideWidth, newHeight);
addButton(langFactory(lng_settings_save), [this] { save(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
addButton(langFactory(lng_settings_save), [=] { save(); });
addButton(langFactory(lng_cancel), [=] { closeBox(); });
if (_invertOrder) {
setTabOrder(_last, _first);
}
_first->setMaxLength(kMaxGroupChannelTitle);
_last->setMaxLength(kMaxGroupChannelTitle);
connect(_first, &Ui::InputField::submitted, this, [this] { submit(); });
connect(_last, &Ui::InputField::submitted, this, [this] { submit(); });
connect(_first, &Ui::InputField::submitted, [=] { submit(); });
connect(_last, &Ui::InputField::submitted, [=] { submit(); });
}
void EditNameBox::setInnerFocus() {
@@ -1052,7 +1065,12 @@ bool EditNameBox::saveSelfFail(const RPCError &error) {
EditBioBox::EditBioBox(QWidget*, not_null<UserData*> self) : BoxContent()
, _dynamicFieldStyle(CreateBioFieldStyle())
, _self(self)
, _bio(this, _dynamicFieldStyle, langFactory(lng_bio_placeholder), _self->about())
, _bio(
this,
_dynamicFieldStyle,
Ui::InputField::Mode::MultiLine,
langFactory(lng_bio_placeholder),
_self->about())
, _countdown(this, QString(), Ui::FlatLabel::InitType::Simple, st::editBioCountdownLabel)
, _about(this, lang(lng_bio_about), Ui::FlatLabel::InitType::Simple, st::aboutRevokePublicLabel) {
}
@@ -1063,13 +1081,15 @@ void EditBioBox::prepare() {
addButton(langFactory(lng_settings_save), [this] { save(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
_bio->setMaxLength(kMaxBioLength);
_bio->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
_bio->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
auto cursor = _bio->textCursor();
cursor.setPosition(_bio->getLastText().size());
_bio->setTextCursor(cursor);
connect(_bio, &Ui::InputArea::submitted, this, [this](bool ctrlShiftEnter) { save(); });
connect(_bio, &Ui::InputArea::resized, this, [this] { updateMaxHeight(); });
connect(_bio, &Ui::InputArea::changed, this, [this] { handleBioUpdated(); });
connect(_bio, &Ui::InputField::submitted, [=] { save(); });
connect(_bio, &Ui::InputField::resized, [=] { updateMaxHeight(); });
connect(_bio, &Ui::InputField::changed, [=] { handleBioUpdated(); });
_bio->setInstantReplaces(Ui::InstantReplaces::Default());
_bio->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
handleBioUpdated();
updateMaxHeight();
}
@@ -1122,7 +1142,12 @@ void EditBioBox::save() {
EditChannelBox::EditChannelBox(QWidget*, not_null<ChannelData*> channel)
: _channel(channel)
, _title(this, st::defaultInputField, langFactory(_channel->isMegagroup() ? lng_dlg_new_group_name : lng_dlg_new_channel_name), _channel->name)
, _description(this, st::newGroupDescription, langFactory(lng_create_group_description), _channel->about())
, _description(
this,
st::newGroupDescription,
Ui::InputField::Mode::MultiLine,
langFactory(lng_create_group_description),
_channel->about())
, _sign(this, lang(lng_edit_sign_messages), channel->addsSignature(), st::defaultBoxCheckbox)
, _inviteGroup(std::make_shared<Ui::RadioenumGroup<Invites>>(channel->anyoneCanAddMembers() ? Invites::Everybody : Invites::OnlyAdmins))
, _inviteEverybody(this, _inviteGroup, Invites::Everybody, lang(lng_edit_group_invites_everybody))
@@ -1133,7 +1158,7 @@ EditChannelBox::EditChannelBox(QWidget*, not_null<ChannelData*> channel)
void EditChannelBox::prepare() {
setTitle(langFactory(_channel->isMegagroup() ? lng_edit_group : lng_edit_channel_title));
addButton(langFactory(lng_settings_save), [this] { onSave(); });
addButton(langFactory(lng_settings_save), [this] { save(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::NameChanged, [this](const Notify::PeerUpdate &update) {
@@ -1145,13 +1170,17 @@ void EditChannelBox::prepare() {
setMouseTracking(true);
_title->setMaxLength(kMaxGroupChannelTitle);
_title->setInstantReplaces(Ui::InstantReplaces::Default());
_title->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
_description->setMaxLength(kMaxChannelDescription);
_description->setInstantReplaces(Ui::InstantReplaces::Default());
_description->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
connect(_description, SIGNAL(resized()), this, SLOT(onDescriptionResized()));
connect(_description, SIGNAL(submitted(bool)), this, SLOT(onSave()));
connect(_description, SIGNAL(cancelled()), this, SLOT(onClose()));
connect(_description, &Ui::InputField::resized, [=] { descriptionResized(); });
connect(_description, &Ui::InputField::submitted, [=] { save(); });
connect(_description, &Ui::InputField::cancelled, [=] { closeBox(); });
connect(_publicLink, SIGNAL(clicked()), this, SLOT(onPublicLink()));
_publicLink->addClickHandler([=] { setupPublicLink(); });
_publicLink->setVisible(_channel->canEditUsername());
_sign->setVisible(canEditSignatures());
_inviteEverybody->setVisible(canEditInvites());
@@ -1167,7 +1196,7 @@ void EditChannelBox::setInnerFocus() {
void EditChannelBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
if (_title->hasFocus()) {
onSave();
save();
}
} else {
BoxContent::keyPressEvent(e);
@@ -1179,7 +1208,7 @@ void EditChannelBox::handleChannelNameChange() {
_sign->setChecked(_channel->addsSignature());
}
void EditChannelBox::onDescriptionResized() {
void EditChannelBox::descriptionResized() {
updateMaxHeight();
update();
}
@@ -1243,7 +1272,7 @@ void EditChannelBox::paintEvent(QPaintEvent *e) {
}
}
void EditChannelBox::onSave() {
void EditChannelBox::save() {
if (_saveTitleRequestId || _saveDescriptionRequestId || _saveSignRequestId || _saveInvitesRequestId) return;
auto title = TextUtilities::PrepareForSending(_title->getLastText());
@@ -1262,7 +1291,7 @@ void EditChannelBox::onSave() {
}
}
void EditChannelBox::onPublicLink() {
void EditChannelBox::setupPublicLink() {
Ui::show(
Box<SetupChannelBox>(_channel, true),
LayerOption::KeepOther);

View File

@@ -18,7 +18,6 @@ namespace Ui {
class FlatLabel;
class InputField;
class PhoneInput;
class InputArea;
class UsernameInput;
class Checkbox;
template <typename Enum>
@@ -37,8 +36,6 @@ enum class PeerFloodType {
QString PeerFloodErrorText(PeerFloodType type);
class AddContactBox : public BoxContent, public RPCSender {
Q_OBJECT
public:
AddContactBox(QWidget*, QString fname = QString(), QString lname = QString(), QString phone = QString());
AddContactBox(QWidget*, UserData *user);
@@ -51,12 +48,10 @@ protected:
void setInnerFocus() override;
private slots:
void onSubmit();
void onSave();
void onRetry();
private:
void submit();
void retry();
void save();
void updateButtons();
void onImportDone(const MTPcontacts_ImportedContacts &res);
@@ -80,8 +75,6 @@ private:
};
class GroupInfoBox : public BoxContent, private MTP::Sender {
Q_OBJECT
public:
GroupInfoBox(QWidget*, CreatingGroupType creating, bool fromTypeChoose);
@@ -91,18 +84,13 @@ protected:
void resizeEvent(QResizeEvent *e) override;
private slots:
void onNext();
void onNameSubmit();
void onDescriptionResized();
void onClose() {
closeBox();
}
private:
void createChannel(const QString &title, const QString &description);
void createGroup(not_null<PeerListBox*> selectUsersBox, const QString &title, const std::vector<not_null<PeerData*>> &users);
void submitName();
void submit();
void descriptionResized();
void updateMaxHeight();
void updateSelected(const QPoint &cursorGlobalPosition);
@@ -111,7 +99,7 @@ private:
object_ptr<Ui::UserpicButton> _photo = { nullptr };
object_ptr<Ui::InputField> _title = { nullptr };
object_ptr<Ui::InputArea> _description = { nullptr };
object_ptr<Ui::InputField> _description = { nullptr };
// group / channel creation
mtpRequestId _creationRequestId = 0;
@@ -120,8 +108,6 @@ private:
};
class SetupChannelBox : public BoxContent, public RPCSender {
Q_OBJECT
public:
SetupChannelBox(QWidget*, ChannelData *channel, bool existing = false);
@@ -137,11 +123,6 @@ protected:
void mousePressEvent(QMouseEvent *e) override;
void leaveEventHook(QEvent *e) override;
private slots:
void onSave();
void onChange();
void onCheck();
private:
enum class Privacy {
Public,
@@ -150,6 +131,9 @@ private:
void privacyChanged(Privacy value);
void updateSelected(const QPoint &cursorGlobalPosition);
void showAddContactsToChannelBox() const;
void handleChange();
void check();
void save();
void onUpdateDone(const MTPBool &result);
bool onUpdateFail(const RPCError &error);
@@ -231,7 +215,7 @@ private:
style::InputField _dynamicFieldStyle;
not_null<UserData*> _self;
object_ptr<Ui::InputArea> _bio;
object_ptr<Ui::InputField> _bio;
object_ptr<Ui::FlatLabel> _countdown;
object_ptr<Ui::FlatLabel> _about;
mtpRequestId _requestId = 0;
@@ -240,8 +224,6 @@ private:
};
class EditChannelBox : public BoxContent, public RPCSender {
Q_OBJECT
public:
EditChannelBox(QWidget*, not_null<ChannelData*> channel);
@@ -253,19 +235,14 @@ protected:
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
private slots:
void onSave();
void onDescriptionResized();
void onPublicLink();
void onClose() {
closeBox();
}
private:
void updateMaxHeight();
bool canEditSignatures() const;
bool canEditInvites() const;
void handleChannelNameChange();
void descriptionResized();
void setupPublicLink();
void save();
void onSaveTitleDone(const MTPUpdates &result);
void onSaveDescriptionDone(const MTPBool &result);
@@ -280,7 +257,7 @@ private:
not_null<ChannelData*> _channel;
object_ptr<Ui::InputField> _title;
object_ptr<Ui::InputArea> _description;
object_ptr<Ui::InputField> _description;
object_ptr<Ui::Checkbox> _sign;
enum class Invites {

View File

@@ -117,6 +117,11 @@ boxLabel: FlatLabel(defaultFlatLabel) {
align: align(topleft);
style: boxLabelStyle;
}
boxDividerLabel: FlatLabel(boxLabel) {
minWidth: 245px;
textFg: windowSubTextFg;
style: defaultTextStyle;
}
countryRowHeight: 36px;
countryRowNameFont: semiboldFont;
@@ -711,3 +716,71 @@ sendMediaFileThumbSize: 64px;
sendMediaFileThumbSkip: 10px;
sendMediaFileNameTop: 7px;
sendMediaFileStatusTop: 37px;
proxyUsePadding: margins(22px, 6px, 22px, 5px);
proxyTryIPv6Padding: margins(22px, 8px, 22px, 5px);
proxyRowPadding: margins(22px, 8px, 8px, 8px);
proxyRowIconSkip: 32px;
proxyRowSkip: 2px;
proxyRowRipple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
}
proxyRowSelectedIcon: icon {{ "passport_ready", windowActiveTextFg }};
proxyRowTitleFg: windowFg;
proxyRowTitlePalette: TextPalette(defaultTextPalette) {
linkFg: windowSubTextFg;
}
proxyRowTitleStyle: TextStyle(defaultTextStyle) {
font: semiboldFont;
linkFont: normalFont;
linkFontOver: normalFont;
}
proxyRowStatusFg: windowSubTextFg;
proxyRowStatusFgOnline: windowActiveTextFg;
proxyRowStatusFgOffline: boxTextFgError;
proxyRowStatusFgAvailable: boxTextFgGood;
proxyRowEdit: IconButton(defaultIconButton) {
width: 40px;
height: 40px;
icon: icon {{ "settings_edit_name", menuIconFg }};
iconOver: icon {{ "settings_edit_name", menuIconFgOver }};
iconPosition: point(12px, 13px);
rippleAreaSize: 40px;
rippleAreaPosition: point(0px, 0px);
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
}
}
proxyEditTitle: FlatLabel(defaultFlatLabel) {
style: TextStyle(defaultTextStyle) {
font: autoDownloadTitleFont;
}
textFg: boxTitleFg;
}
proxyEditTitlePadding: margins(22px, 16px, 22px, 0px);
proxyEditTypePadding: margins(22px, 4px, 22px, 8px);
proxyEditInputPadding: margins(22px, 0px, 22px, 0px);
proxyEditSkip: 16px;
proxyEmptyListLabel: FlatLabel(defaultFlatLabel) {
align: align(top);
textFg: windowSubTextFg;
}
proxyEmptyListPadding: margins(22px, 48px, 22px, 0px);
proxyCheckingPosition: point(2px, 5px);
proxyCheckingSkip: 6px;
proxyCheckingAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
color: windowSubTextFg;
thickness: 1px;
size: size(8px, 8px);
}
proxyDropdownDownPosition: point(-2px, 35px);
proxyDropdownUpPosition: point(-2px, 20px);
proxyAboutPadding: margins(22px, 7px, 22px, 14px);
proxyAboutSponsorPadding: margins(22px, 7px, 22px, 0px);
markdownLinkFieldPadding: margins(22px, 0px, 22px, 10px);

View File

@@ -7,9 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/change_phone_box.h"
#include <rpl/filter.h>
#include <rpl/mappers.h>
#include <rpl/take.h>
#include "lang/lang_keys.h"
#include "styles/style_boxes.h"
#include "ui/widgets/labels.h"
@@ -128,7 +125,7 @@ void ChangePhoneBox::EnterPhone::prepare() {
_phone->resize(st::boxWidth - 2 * st::boxPadding.left(), _phone->height());
_phone->moveToLeft(st::boxPadding.left(), st::boxLittleSkip);
connect(_phone, &Ui::PhoneInput::submitted, this, [this] { submit(); });
connect(_phone, &Ui::PhoneInput::submitted, [=] { submit(); });
auto description = object_ptr<Ui::FlatLabel>(this, lang(lng_change_phone_new_description), Ui::FlatLabel::InitType::Simple, st::changePhoneLabel);
auto errorSkip = st::boxLittleSkip + st::changePhoneError.style.font->height;
@@ -228,12 +225,12 @@ void ChangePhoneBox::EnterCode::prepare() {
auto phoneValue = QString();
_code.create(this, st::defaultInputField, langFactory(lng_change_phone_code_title), phoneValue);
_code->setAutoSubmit(_codeLength, [this] { submit(); });
_code->setChangedCallback([this] { hideError(); });
_code->setAutoSubmit(_codeLength, [=] { submit(); });
_code->setChangedCallback([=] { hideError(); });
_code->resize(st::boxWidth - 2 * st::boxPadding.left(), _code->height());
_code->moveToLeft(st::boxPadding.left(), description->bottomNoMargins());
connect(_code, &Ui::InputField::submitted, this, [this] { submit(); });
connect(_code, &Ui::InputField::submitted, [=] { submit(); });
setDimensions(st::boxWidth, countHeight());
@@ -242,8 +239,8 @@ void ChangePhoneBox::EnterCode::prepare() {
updateCall();
}
addButton(langFactory(lng_change_phone_new_submit), [this] { submit(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
addButton(langFactory(lng_change_phone_new_submit), [=] { submit(); });
addButton(langFactory(lng_cancel), [=] { closeBox(); });
}
int ChangePhoneBox::EnterCode::countHeight() {

View File

@@ -119,9 +119,19 @@ void ConfirmBox::prepare() {
textUpdated();
}
void ConfirmBox::setMaxLineCount(int count) {
if (_maxLineCount != count) {
_maxLineCount = count;
textUpdated();
}
}
void ConfirmBox::textUpdated() {
_textWidth = st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right();
_textHeight = qMin(_text.countHeight(_textWidth), 16 * st::boxLabelStyle.lineHeight);
_textHeight = _text.countHeight(_textWidth);
if (_maxLineCount > 0) {
accumulate_min(_textHeight, _maxLineCount * st::boxLabelStyle.lineHeight);
}
setDimensions(st::boxWidth, st::boxPadding.top() + _textHeight + st::boxPadding.bottom());
setMouseTracking(_text.hasLinks());
@@ -198,7 +208,11 @@ void ConfirmBox::paintEvent(QPaintEvent *e) {
// draw box title / text
p.setPen(st::boxTextFg);
_text.drawLeftElided(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), 16, style::al_left);
if (_maxLineCount > 0) {
_text.drawLeftElided(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), _maxLineCount, style::al_left);
} else {
_text.drawLeft(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), style::al_left);
}
}
InformBox::InformBox(QWidget*, const QString &text, base::lambda<void()> closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, lang(lng_box_ok), std::move(closedCallback)) {
@@ -641,7 +655,7 @@ ConfirmInviteBox::ConfirmInviteBox(QWidget*, const QString &title, bool isChanne
}
void ConfirmInviteBox::prepare() {
addButton(langFactory(lng_group_invite_join), [this] {
addButton(langFactory(lng_group_invite_join), [] {
if (auto main = App::main()) {
main->onInviteImport();
}

View File

@@ -31,6 +31,8 @@ public:
_strictCancel = strictCancel;
}
void setMaxLineCount(int count);
// ClickHandlerHost interface
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
@@ -55,6 +57,7 @@ private:
void confirmed();
void init(const QString &text);
void textUpdated();
void updateHover();
QString _confirmText;
QString _cancelText;
@@ -64,8 +67,7 @@ private:
Text _text;
int _textWidth = 0;
int _textHeight = 0;
void updateHover();
int _maxLineCount = 16;
QPoint _lastMousePos;

View File

@@ -198,17 +198,17 @@ void ConfirmPhoneBox::prepare() {
_about->setMarkedText(aboutText);
_code.create(this, st::confirmPhoneCodeField, langFactory(lng_code_ph));
_code->setAutoSubmit(_sentCodeLength, [this] { onSendCode(); });
_code->setChangedCallback([this] { showError(QString()); });
_code->setAutoSubmit(_sentCodeLength, [=] { sendCode(); });
_code->setChangedCallback([=] { showError(QString()); });
setTitle(langFactory(lng_confirm_phone_title));
addButton(langFactory(lng_confirm_phone_send), [this] { onSendCode(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
addButton(langFactory(lng_confirm_phone_send), [=] { sendCode(); });
addButton(langFactory(lng_cancel), [=] { closeBox(); });
setDimensions(st::boxWidth, st::usernamePadding.top() + _code->height() + st::usernameSkip + _about->height() + st::usernameSkip);
connect(_code, SIGNAL(submitted(bool)), this, SLOT(onSendCode()));
connect(_code, &Ui::InputField::submitted, [=] { sendCode(); });
showChildren();
}
@@ -217,7 +217,7 @@ void ConfirmPhoneBox::callDone(const MTPauth_SentCode &result) {
_call.callDone();
}
void ConfirmPhoneBox::onSendCode() {
void ConfirmPhoneBox::sendCode() {
if (_sendCodeRequestId) {
return;
}

View File

@@ -82,16 +82,11 @@ private:
};
class ConfirmPhoneBox : public BoxContent, public RPCSender {
Q_OBJECT
public:
static void start(const QString &phone, const QString &hash);
~ConfirmPhoneBox();
private slots:
void onSendCode();
protected:
void prepare() override;
void setInnerFocus() override;
@@ -103,6 +98,7 @@ private:
ConfirmPhoneBox(QWidget*, const QString &phone, const QString &hash);
friend class object_ptr<ConfirmPhoneBox>;
void sendCode();
void sendCall();
void checkPhoneAndHash();

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "boxes/abstract_box.h"
#include "base/timer.h"
#include "mtproto/connection_abstract.h"
namespace Ui {
class InputField;
@@ -20,43 +22,6 @@ template <typename Enum>
class Radioenum;
} // namespace Ui
class ConnectionBox : public BoxContent {
Q_OBJECT
public:
ConnectionBox(QWidget *parent);
static void ShowApplyProxyConfirmation(const QMap<QString, QString> &fields);
protected:
void prepare() override;
void setInnerFocus() override;
void resizeEvent(QResizeEvent *e) override;
private slots:
void onSubmit();
void onFieldFocus();
void onSave();
private:
void typeChanged(DBIConnectionType type);
void updateControlsVisibility();
void updateControlsPosition();
bool badProxyValue() const;
object_ptr<Ui::InputField> _hostInput;
object_ptr<Ui::PortInput> _portInput;
object_ptr<Ui::InputField> _userInput;
object_ptr<Ui::PasswordInput> _passwordInput;
std::shared_ptr<Ui::RadioenumGroup<DBIConnectionType>> _typeGroup;
object_ptr<Ui::Radioenum<DBIConnectionType>> _autoRadio;
object_ptr<Ui::Radioenum<DBIConnectionType>> _httpProxyRadio;
object_ptr<Ui::Radioenum<DBIConnectionType>> _tcpProxyRadio;
object_ptr<Ui::Checkbox> _tryIPv6;
};
class AutoDownloadBox : public BoxContent {
Q_OBJECT
@@ -84,3 +49,93 @@ private:
int _sectionHeight = 0;
};
class ProxiesBoxController : public base::Subscriber {
public:
using Type = ProxyData::Type;
ProxiesBoxController();
static void ShowApplyConfirmation(
Type type,
const QMap<QString, QString> &fields);
static object_ptr<BoxContent> CreateOwningBox();
object_ptr<BoxContent> create();
enum class ItemState {
Connecting,
Online,
Checking,
Available,
Unavailable
};
struct ItemView {
int id = 0;
QString type;
QString host;
uint32 port = 0;
int ping = 0;
bool selected = false;
bool deleted = false;
bool supportsShare = false;
bool supportsCalls = false;
ItemState state = ItemState::Checking;
};
void deleteItem(int id);
void restoreItem(int id);
void shareItem(int id);
void applyItem(int id);
object_ptr<BoxContent> editItemBox(int id);
object_ptr<BoxContent> addNewItemBox();
bool setProxyEnabled(bool enabled);
void setProxyForCalls(bool enabled);
void setTryIPv6(bool enabled);
rpl::producer<bool> proxyEnabledValue() const;
rpl::producer<ItemView> views() const;
~ProxiesBoxController();
private:
using Checker = MTP::internal::ConnectionPointer;
struct Item {
int id = 0;
ProxyData data;
bool deleted = false;
Checker checker;
Checker checkerv6;
ItemState state = ItemState::Checking;
int ping = 0;
};
std::vector<Item>::iterator findById(int id);
std::vector<Item>::iterator findByProxy(const ProxyData &proxy);
void setDeleted(int id, bool deleted);
void updateView(const Item &item);
void share(const ProxyData &proxy);
void saveDelayed();
void refreshChecker(Item &item);
void setupChecker(int id, const Checker &checker);
void replaceItemWith(
std::vector<Item>::iterator which,
std::vector<Item>::iterator with);
void replaceItemValue(
std::vector<Item>::iterator which,
const ProxyData &proxy);
void addNewItem(const ProxyData &proxy);
int _idCounter = 0;
std::vector<Item> _list;
rpl::event_stream<ItemView> _views;
base::Timer _saveTimer;
rpl::event_stream<bool> _proxyEnabledChanges;
ProxyData _lastSelectedProxy;
bool _lastSelectedProxyUsed = false;
};

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h"
#include "data/data_document.h"
#include "lang/lang_keys.h"
#include "chat_helpers/message_field.h"
#include "window/window_controller.h"
#include "mainwidget.h"
#include "layout.h"
@@ -24,8 +25,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
EditCaptionBox::EditCaptionBox(
QWidget*,
not_null<Window::Controller*> controller,
not_null<HistoryItem*> item)
: _msgId(item->fullId()) {
: _controller(controller)
, _msgId(item->fullId()) {
Expects(item->media() != nullptr);
Expects(item->media()->allowsEditCaption());
@@ -50,7 +53,11 @@ EditCaptionBox::EditCaptionBox(
}
doc = document;
}
auto caption = item->originalText().text;
const auto original = item->originalText();
const auto editData = TextWithTags {
original.text,
ConvertEntitiesToTextTags(original.entities)
};
if (!_animated && (dimensions.isEmpty() || doc || image->isNull())) {
if (image->isNull()) {
@@ -130,9 +137,19 @@ EditCaptionBox::EditCaptionBox(
}
Assert(_animated || _photo || _doc);
_field.create(this, st::confirmCaptionArea, langFactory(lng_photo_caption), caption);
_field.create(
this,
st::confirmCaptionArea,
Ui::InputField::Mode::MultiLine,
langFactory(lng_photo_caption),
editData);
_field->setMaxLength(MaxPhotoCaption);
_field->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
_field->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
_field->setInstantReplaces(Ui::InstantReplaces::Default());
_field->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
_field->setMarkdownReplacesEnabled(rpl::single(true));
_field->setEditLinkCallback(
DefaultEditLinkCallback(_controller, _field));
}
void EditCaptionBox::prepareGifPreview(DocumentData *document) {
@@ -177,13 +194,9 @@ void EditCaptionBox::prepare() {
addButton(langFactory(lng_cancel), [this] { closeBox(); });
updateBoxSize();
connect(_field, &Ui::InputArea::submitted, this, [this] { save(); });
connect(_field, &Ui::InputArea::cancelled, this, [this] {
closeBox();
});
connect(_field, &Ui::InputArea::resized, this, [this] {
captionResized();
});
connect(_field, &Ui::InputField::submitted, [=] { save(); });
connect(_field, &Ui::InputField::cancelled, [=] { closeBox(); });
connect(_field, &Ui::InputField::resized, [=] { captionResized(); });
auto cursor = _field->textCursor();
cursor.movePosition(QTextCursor::End);
@@ -332,17 +345,30 @@ void EditCaptionBox::save() {
if (_previewCancelled) {
flags |= MTPmessages_EditMessage::Flag::f_no_webpage;
}
MTPVector<MTPMessageEntity> sentEntities;
const auto textWithTags = _field->getTextWithAppliedMarkdown();
auto sending = TextWithEntities{
textWithTags.text,
ConvertTextTagsToEntities(textWithTags.tags)
};
const auto prepareFlags = Ui::ItemTextOptions(
item->history(),
App::self()).flags;
TextUtilities::PrepareForSending(sending, prepareFlags);
TextUtilities::Trim(sending);
const auto sentEntities = TextUtilities::EntitiesToMTP(
sending.entities,
TextUtilities::ConvertOption::SkipLocal);
if (!sentEntities.v.isEmpty()) {
flags |= MTPmessages_EditMessage::Flag::f_entities;
}
auto text = TextUtilities::PrepareForSending(_field->getLastText(), TextUtilities::PrepareTextOption::CheckLinks);
_saveRequestId = MTP::send(
MTPmessages_EditMessage(
MTP_flags(flags),
item->history()->peer->input,
MTP_int(item->id),
MTP_string(text),
MTP_string(sending.text),
MTPInputMedia(),
MTPnullMarkup,
sentEntities,
MTP_inputGeoPointEmpty()),

View File

@@ -9,17 +9,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
namespace Window {
class Controller;
} // namespace Window
namespace Data {
class Media;
} // namespace Data
namespace Ui {
class InputArea;
class InputField;
} // namespace Ui
class EditCaptionBox : public BoxContent, public RPCSender {
public:
EditCaptionBox(QWidget*, not_null<HistoryItem*> item);
EditCaptionBox(
QWidget*,
not_null<Window::Controller*> controller,
not_null<HistoryItem*> item);
protected:
void prepare() override;
@@ -41,6 +48,7 @@ private:
int errorTopSkip() const;
not_null<Window::Controller*> _controller;
FullMsgId _msgId;
bool _animated = false;
bool _photo = false;
@@ -49,7 +57,7 @@ private:
QPixmap _thumb;
Media::Clip::ReaderPointer _gifPreview;
object_ptr<Ui::InputArea> _field = { nullptr };
object_ptr<Ui::InputField> _field = { nullptr };
int _thumbx = 0;
int _thumbw = 0;

View File

@@ -630,34 +630,36 @@ EditColorBox::EditColorBox(QWidget*, const QString &title, QColor current) : Box
}
void EditColorBox::prepare() {
setTitle([this] { return _title; });
setTitle([=] { return _title; });
connect(_hueField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_saturationField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_brightnessField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_redField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_greenField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_blueField, SIGNAL(changed()), this, SLOT(onFieldChanged()));
connect(_result, SIGNAL(changed()), this, SLOT(onFieldChanged()));
const auto changed = [=] { fieldChanged(); };
connect(_hueField, &Ui::MaskedInputField::changed, changed);
connect(_saturationField, &Ui::MaskedInputField::changed, changed);
connect(_brightnessField, &Ui::MaskedInputField::changed, changed);
connect(_redField, &Ui::MaskedInputField::changed, changed);
connect(_greenField, &Ui::MaskedInputField::changed, changed);
connect(_blueField, &Ui::MaskedInputField::changed, changed);
connect(_result, &Ui::MaskedInputField::changed, changed);
connect(_hueField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
connect(_saturationField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
connect(_brightnessField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
connect(_redField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
connect(_greenField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
connect(_blueField, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
connect(_result, SIGNAL(submitted(bool)), this, SLOT(onFieldSubmitted()));
const auto submitted = [=] { fieldSubmitted(); };
connect(_hueField, &Ui::MaskedInputField::submitted, submitted);
connect(_saturationField, &Ui::MaskedInputField::submitted, submitted);
connect(_brightnessField, &Ui::MaskedInputField::submitted, submitted);
connect(_redField, &Ui::MaskedInputField::submitted, submitted);
connect(_greenField, &Ui::MaskedInputField::submitted, submitted);
connect(_blueField, &Ui::MaskedInputField::submitted, submitted);
connect(_result, &Ui::MaskedInputField::submitted, submitted);
addButton(langFactory(lng_settings_save), [this] { saveColor(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
addButton(langFactory(lng_settings_save), [=] { saveColor(); });
addButton(langFactory(lng_cancel), [=] { closeBox(); });
auto height = st::colorEditSkip + st::colorPickerSize + st::colorEditSkip + st::colorSliderWidth + st::colorEditSkip;
setDimensions(st::colorEditWidth, height);
subscribe(_picker->changed(), [this] { updateFromControls(); });
subscribe(_hueSlider->changed(), [this] { updateFromControls(); });
subscribe(_opacitySlider->changed(), [this] { updateFromControls(); });
subscribe(boxClosing, [this] {
subscribe(_picker->changed(), [=] { updateFromControls(); });
subscribe(_hueSlider->changed(), [=] { updateFromControls(); });
subscribe(_opacitySlider->changed(), [=] { updateFromControls(); });
subscribe(boxClosing, [=] {
if (_cancelCallback) {
_cancelCallback();
}
@@ -670,7 +672,7 @@ void EditColorBox::setInnerFocus() {
_result->selectAll();
}
void EditColorBox::onFieldChanged() {
void EditColorBox::fieldChanged() {
auto emitter = sender();
auto checkHSVSender = [this, emitter](QObject *field) {
if (emitter == field) {
@@ -693,7 +695,7 @@ void EditColorBox::onFieldChanged() {
}
}
void EditColorBox::onFieldSubmitted() {
void EditColorBox::fieldSubmitted() {
Ui::MaskedInputField *fields[] = {
_hueField,
_saturationField,

View File

@@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
class EditColorBox : public BoxContent {
Q_OBJECT
public:
EditColorBox(QWidget*, const QString &title, QColor current = QColor(255, 255, 255));
@@ -37,12 +35,10 @@ protected:
void setInnerFocus() override;
private slots:
void onFieldChanged();
void onFieldSubmitted();
private:
void saveColor();
void fieldChanged();
void fieldSubmitted();
void updateFromColor(QColor color);
void updateControlsFromColor();

View File

@@ -469,7 +469,7 @@ void EditRestrictedBox::createUntilVariants() {
_untilVariants.back()->setDisabled(true);
}
};
auto addCustomVariant = [this, addVariant](TimeId until, TimeId from, TimeId to) {
auto addCustomVariant = [addVariant](TimeId until, TimeId from, TimeId to) {
if (!ChannelData::IsRestrictedForever(until) && until > from && until <= to) {
addVariant(
until,

View File

@@ -95,7 +95,7 @@ void EditPrivacyBox::prepare() {
int EditPrivacyBox::resizeGetHeight(int newWidth) {
auto top = 0;
auto layoutRow = [this, newWidth, &top](auto &widget, style::margins padding) {
auto layoutRow = [&](auto &widget, style::margins padding) {
if (!widget) return;
widget->resizeToNaturalWidth(newWidth - padding.left() - padding.right());
widget->moveToLeft(padding.left(), top + padding.top());
@@ -137,7 +137,7 @@ int EditPrivacyBox::countDefaultHeight(int newWidth) {
}
return st::editPrivacyOptionMargin.top() + st::defaultCheck.diameter + st::editPrivacyOptionMargin.bottom();
};
auto labelHeight = [this, newWidth](const QString &text, const style::FlatLabel &st, style::margins padding) {
auto labelHeight = [newWidth](const QString &text, const style::FlatLabel &st, style::margins padding) {
if (text.isEmpty()) {
return 0;
}

View File

@@ -8,7 +8,8 @@ Copyright (C) 2017, Nicholas Guriev <guriev-ns@ya.ru>
#include "boxes/mute_settings_box.h"
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "auth_session.h"
#include "data/data_session.h"
#include "styles/style_boxes.h"
#include "ui/special_buttons.h"
#include "ui/widgets/checkbox.h"
@@ -69,10 +70,8 @@ void MuteSettingsBox::prepare() {
addButton(langFactory(lng_box_ok), [this, group] {
auto muteForSeconds = group->value() * 3600;
App::main()->updateNotifySettings(
Auth().data().updateNotifySettings(
_peer,
Data::NotifySettings::MuteChange::Mute,
Data::NotifySettings::SilentPostsChange::Ignore,
muteForSeconds);
closeBox();
});

View File

@@ -43,8 +43,8 @@ PasscodeBox::PasscodeBox(QWidget*, const QByteArray &newSalt, const QByteArray &
}
void PasscodeBox::prepare() {
addButton(langFactory(_turningOff ? lng_passcode_remove_button : lng_settings_save), [this] { onSave(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
addButton(langFactory(_turningOff ? lng_passcode_remove_button : lng_settings_save), [=] { save(); });
addButton(langFactory(lng_cancel), [=] { closeBox(); });
_about.setRichText(st::passcodeTextStyle, lang(_cloudPwd ? lng_cloud_password_about : lng_passcode_about));
_aboutHeight = _about.countHeight(st::boxWidth - st::boxPadding.left() * 1.5);
@@ -65,19 +65,20 @@ void PasscodeBox::prepare() {
}
}
connect(_oldPasscode, SIGNAL(changed()), this, SLOT(onOldChanged()));
connect(_newPasscode, SIGNAL(changed()), this, SLOT(onNewChanged()));
connect(_reenterPasscode, SIGNAL(changed()), this, SLOT(onNewChanged()));
connect(_passwordHint, SIGNAL(changed()), this, SLOT(onNewChanged()));
connect(_recoverEmail, SIGNAL(changed()), this, SLOT(onEmailChanged()));
connect(_oldPasscode, &Ui::MaskedInputField::changed, [=] { oldChanged(); });
connect(_newPasscode, &Ui::MaskedInputField::changed, [=] { newChanged(); });
connect(_reenterPasscode, &Ui::MaskedInputField::changed, [=] { newChanged(); });
connect(_passwordHint, &Ui::InputField::changed, [=] { newChanged(); });
connect(_recoverEmail, &Ui::InputField::changed, [=] { emailChanged(); });
connect(_oldPasscode, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(_newPasscode, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(_reenterPasscode, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(_passwordHint, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(_recoverEmail, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
const auto fieldSubmit = [=] { submit(); };
connect(_oldPasscode, &Ui::MaskedInputField::submitted, fieldSubmit);
connect(_newPasscode, &Ui::MaskedInputField::submitted, fieldSubmit);
connect(_reenterPasscode, &Ui::MaskedInputField::submitted, fieldSubmit);
connect(_passwordHint, &Ui::InputField::submitted, fieldSubmit);
connect(_recoverEmail, &Ui::InputField::submitted, fieldSubmit);
connect(_recover, SIGNAL(clicked()), this, SLOT(onRecoverByEmail()));
_recover->addClickHandler([=] { recoverByEmail(); });
bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode();
_oldPasscode->setVisible(_turningOff || has);
@@ -88,11 +89,11 @@ void PasscodeBox::prepare() {
_recoverEmail->setVisible(!_turningOff && _cloudPwd && _curSalt.isEmpty());
}
void PasscodeBox::onSubmit() {
void PasscodeBox::submit() {
bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode();
if (_oldPasscode->hasFocus()) {
if (_turningOff) {
onSave();
save();
} else {
_newPasscode->setFocus();
}
@@ -110,16 +111,16 @@ void PasscodeBox::onSubmit() {
} else if (!_passwordHint->isHidden()) {
_passwordHint->setFocus();
} else {
onSave();
save();
}
} else if (_passwordHint->hasFocus()) {
if (_recoverEmail->isHidden()) {
onSave();
save();
} else {
_recoverEmail->setFocus();
}
} else if (_recoverEmail->hasFocus()) {
onSave();
save();
}
}
@@ -226,7 +227,7 @@ bool PasscodeBox::setPasswordFail(const RPCError &error) {
emit reloadPassword();
closeBox();
} else {
onBadOldPasscode();
badOldPasscode();
}
} else if (err == qstr("NEW_PASSWORD_BAD")) {
_newPasscode->setFocus();
@@ -248,7 +249,7 @@ bool PasscodeBox::setPasswordFail(const RPCError &error) {
return true;
}
void PasscodeBox::onSave(bool force) {
void PasscodeBox::save(bool force) {
if (_setRequest) return;
QString old = _oldPasscode->text(), pwd = _newPasscode->text(), conf = _reenterPasscode->text();
@@ -268,7 +269,7 @@ void PasscodeBox::onSave(bool force) {
} else {
cSetPasscodeBadTries(cPasscodeBadTries() + 1);
cSetPasscodeLastTry(getms(true));
onBadOldPasscode();
badOldPasscode();
return;
}
}
@@ -306,7 +307,7 @@ void PasscodeBox::onSave(bool force) {
if (!_recoverEmail->isHidden() && email.isEmpty() && !force) {
_skipEmailWarning = true;
_replacedBy = Ui::show(Box<ConfirmBox>(lang(lng_cloud_password_about_recover), lang(lng_cloud_password_skip_email), st::attentionBoxButton, base::lambda_guarded(this, [this] {
onSave(true);
save(true);
})), LayerOption::KeepOther);
} else {
QByteArray newPasswordData = pwd.isEmpty() ? QByteArray() : (_newSalt + pwd.toUtf8() + _newSalt);
@@ -337,7 +338,7 @@ void PasscodeBox::onSave(bool force) {
}
}
void PasscodeBox::onBadOldPasscode() {
void PasscodeBox::badOldPasscode() {
_oldPasscode->selectAll();
_oldPasscode->setFocus();
_oldPasscode->showError();
@@ -348,7 +349,7 @@ void PasscodeBox::onBadOldPasscode() {
update();
}
void PasscodeBox::onOldChanged() {
void PasscodeBox::oldChanged() {
if (!_oldError.isEmpty()) {
_oldError = QString();
if (_hasRecovery && _hintText.isEmpty()) {
@@ -358,21 +359,21 @@ void PasscodeBox::onOldChanged() {
}
}
void PasscodeBox::onNewChanged() {
void PasscodeBox::newChanged() {
if (!_newError.isEmpty()) {
_newError = QString();
update();
}
}
void PasscodeBox::onEmailChanged() {
void PasscodeBox::emailChanged() {
if (!_emailError.isEmpty()) {
_emailError = QString();
update();
}
}
void PasscodeBox::onRecoverByEmail() {
void PasscodeBox::recoverByEmail() {
if (_pattern.isEmpty()) {
_pattern = "-";
MTP::send(MTPauth_RequestPasswordRecovery(), rpcDone(&PasscodeBox::recoverStarted), rpcFail(&PasscodeBox::recoverStartFail));
@@ -381,18 +382,19 @@ void PasscodeBox::onRecoverByEmail() {
}
}
void PasscodeBox::onRecoverExpired() {
void PasscodeBox::recoverExpired() {
_pattern = QString();
}
void PasscodeBox::recover() {
if (_pattern == "-") return;
_replacedBy = Ui::show(
const auto box = Ui::show(
Box<RecoverBox>(_pattern),
LayerOption::KeepOther);
connect(_replacedBy, SIGNAL(reloadPassword()), this, SIGNAL(reloadPassword()));
connect(_replacedBy, SIGNAL(recoveryExpired()), this, SLOT(onRecoverExpired()));
connect(box, &RecoverBox::reloadPassword, this, &PasscodeBox::reloadPassword);
connect(box, &RecoverBox::recoveryExpired, this, &PasscodeBox::recoverExpired);
_replacedBy = box;
}
void PasscodeBox::recoverStarted(const MTPauth_PasswordRecovery &result) {
@@ -416,13 +418,13 @@ RecoverBox::RecoverBox(QWidget*, const QString &pattern)
void RecoverBox::prepare() {
setTitle(langFactory(lng_signin_recover_title));
addButton(langFactory(lng_passcode_submit), [this] { onSubmit(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
addButton(langFactory(lng_passcode_submit), [=] { submit(); });
addButton(langFactory(lng_cancel), [=] { closeBox(); });
setDimensions(st::boxWidth, st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine + _recoverCode->height() + st::passcodeTextLine);
connect(_recoverCode, SIGNAL(changed()), this, SLOT(onCodeChanged()));
connect(_recoverCode, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(_recoverCode, &Ui::InputField::changed, [=] { codeChanged(); });
connect(_recoverCode, &Ui::InputField::submitted, [=] { submit(); });
}
void RecoverBox::paintEvent(QPaintEvent *e) {
@@ -452,7 +454,7 @@ void RecoverBox::setInnerFocus() {
_recoverCode->setFocusFast();
}
void RecoverBox::onSubmit() {
void RecoverBox::submit() {
if (_submitRequest) return;
QString code = _recoverCode->getLastText().trimmed();
@@ -465,7 +467,7 @@ void RecoverBox::onSubmit() {
_submitRequest = MTP::send(MTPauth_RecoverPassword(MTP_string(code)), rpcDone(&RecoverBox::codeSubmitDone, true), rpcFail(&RecoverBox::codeSubmitFail));
}
void RecoverBox::onCodeChanged() {
void RecoverBox::codeChanged() {
_error = QString();
update();
}

View File

@@ -22,16 +22,6 @@ public:
PasscodeBox(QWidget*, bool turningOff);
PasscodeBox(QWidget*, const QByteArray &newSalt, const QByteArray &curSalt, bool hasRecovery, const QString &hint, bool turningOff = false);
private slots:
void onSave(bool force = false);
void onBadOldPasscode();
void onOldChanged();
void onNewChanged();
void onEmailChanged();
void onRecoverByEmail();
void onRecoverExpired();
void onSubmit();
signals:
void reloadPassword();
@@ -43,7 +33,15 @@ protected:
void resizeEvent(QResizeEvent *e) override;
private:
void submit();
void closeReplacedBy();
void oldChanged();
void newChanged();
void emailChanged();
void save(bool force = false);
void badOldPasscode();
void recoverByEmail();
void recoverExpired();
void setPasswordDone(const MTPBool &result);
bool setPasswordFail(const RPCError &error);
@@ -84,10 +82,6 @@ class RecoverBox : public BoxContent, public RPCSender {
public:
RecoverBox(QWidget*, const QString &pattern);
public slots:
void onSubmit();
void onCodeChanged();
signals:
void reloadPassword();
void recoveryExpired();
@@ -100,6 +94,8 @@ protected:
void resizeEvent(QResizeEvent *e) override;
private:
void submit();
void codeChanged();
void codeSubmitDone(bool recover, const MTPauth_Authorization &result);
bool codeSubmitFail(const RPCError &error);

View File

@@ -46,7 +46,7 @@ void PeerListBox::createMultiSelect() {
) | rpl::start_with_next(
[this] { updateScrollSkips(); },
lifetime());
_select->entity()->setSubmittedCallback([this](bool chtrlShiftEnter) { content()->submitted(); });
_select->entity()->setSubmittedCallback([this](Qt::KeyboardModifiers) { content()->submitted(); });
_select->entity()->setQueryChangedCallback([this](const QString &query) { searchQueryChanged(query); });
_select->entity()->setItemRemovedCallback([this](uint64 itemId) {
if (auto peer = App::peerLoaded(itemId)) {

View File

@@ -731,7 +731,7 @@ void EditChatAdminsBoxController::Start(not_null<ChatData*> chat) {
}
void AddBotToGroupBoxController::Start(not_null<UserData*> bot) {
auto initBox = [bot](not_null<PeerListBox*> box) {
auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); });
};
Ui::show(Box<PeerListBox>(std::make_unique<AddBotToGroupBoxController>(bot), std::move(initBox)));

View File

@@ -38,6 +38,8 @@ namespace {
constexpr auto kUsernameCheckTimeout = TimeMs(200);
constexpr auto kMinUsernameLength = 5;
constexpr auto kMaxGroupChannelTitle = 255; // See also add_contact_box.
constexpr auto kMaxChannelDescription = 255; // See also add_contact_box.
class Controller
: private MTP::Sender
@@ -70,7 +72,7 @@ private:
};
struct Controls {
Ui::InputField *title = nullptr;
Ui::InputArea *description = nullptr;
Ui::InputField *description = nullptr;
Ui::UserpicButton *photo = nullptr;
rpl::lifetime initialPhotoImageWaiting;
@@ -298,11 +300,15 @@ object_ptr<Ui::RpWidget> Controller::createTitleEdit() {
: lng_dlg_new_channel_name),
_peer->name),
st::editPeerTitleMargins);
result->entity()->setMaxLength(kMaxGroupChannelTitle);
result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
result->entity()->setInstantReplacesEnabled(
Global::ReplaceEmojiValue());
QObject::connect(
result->entity(),
&Ui::InputField::submitted,
[this] { submitTitle(); });
[=] { submitTitle(); });
_controls.title = result->entity();
return std::move(result);
@@ -316,19 +322,24 @@ object_ptr<Ui::RpWidget> Controller::createDescriptionEdit() {
return nullptr;
}
auto result = object_ptr<Ui::PaddingWrap<Ui::InputArea>>(
auto result = object_ptr<Ui::PaddingWrap<Ui::InputField>>(
_wrap,
object_ptr<Ui::InputArea>(
object_ptr<Ui::InputField>(
_wrap,
st::editPeerDescription,
Ui::InputField::Mode::MultiLine,
langFactory(lng_create_group_description),
channel->about()),
st::editPeerDescriptionMargins);
result->entity()->setMaxLength(kMaxChannelDescription);
result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
result->entity()->setInstantReplacesEnabled(
Global::ReplaceEmojiValue());
QObject::connect(
result->entity(),
&Ui::InputArea::submitted,
[this] { submitDescription(); });
&Ui::InputField::submitted,
[=] { submitDescription(); });
_controls.description = result->entity();
return std::move(result);
@@ -1252,11 +1263,9 @@ void Controller::saveTitle() {
continueSave();
return;
}
_controls.title->showError();
if (type == qstr("NO_CHAT_TITLE")) {
_controls.title->showError();
_box->scrollToWidget(_controls.title);
} else {
_controls.title->setFocus();
}
cancelSave();
};
@@ -1302,7 +1311,7 @@ void Controller::saveDescription() {
successCallback();
return;
}
_controls.description->setFocus();
_controls.description->showError();
cancelSave();
}).send();
}

View File

@@ -60,7 +60,7 @@ void RateCallBox::ratingChanged(int value) {
Expects(value > 0 && value <= kMaxRating);
if (!_rating) {
clearButtons();
addButton(langFactory(lng_send_button), [this] { onSend(); });
addButton(langFactory(lng_send_button), [this] { send(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
}
_rating = value;
@@ -71,16 +71,20 @@ void RateCallBox::ratingChanged(int value) {
}
if (value < kMaxRating) {
if (!_comment) {
_comment.create(this, st::callRatingComment, langFactory(lng_call_rate_comment));
_comment.create(
this,
st::callRatingComment,
Ui::InputField::Mode::MultiLine,
langFactory(lng_call_rate_comment));
_comment->show();
_comment->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
_comment->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
_comment->setMaxLength(MaxPhotoCaption);
_comment->resize(width() - (st::callRatingPadding.left() + st::callRatingPadding.right()), _comment->height());
updateMaxHeight();
connect(_comment, SIGNAL(resized()), this, SLOT(onCommentResized()));
connect(_comment, SIGNAL(submitted(bool)), this, SLOT(onSend()));
connect(_comment, SIGNAL(cancelled()), this, SLOT(onClose()));
connect(_comment, &Ui::InputField::resized, [=] { commentResized(); });
connect(_comment, &Ui::InputField::submitted, [=] { send(); });
connect(_comment, &Ui::InputField::cancelled, [=] { closeBox(); });
}
_comment->setFocusFast();
} else if (_comment) {
@@ -97,13 +101,14 @@ void RateCallBox::setInnerFocus() {
}
}
void RateCallBox::onCommentResized() {
void RateCallBox::commentResized() {
updateMaxHeight();
update();
}
void RateCallBox::onSend() {
void RateCallBox::send() {
Expects(_rating > 0 && _rating <= kMaxRating);
if (_requestId) {
return;
}

View File

@@ -11,24 +11,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/sender.h"
namespace Ui {
class InputArea;
class InputField;
class FlatLabel;
class IconButton;
} // namespace Ui
class RateCallBox : public BoxContent, private MTP::Sender {
Q_OBJECT
public:
RateCallBox(QWidget*, uint64 callId, uint64 callAccessHash);
private slots:
void onSend();
void onCommentResized();
void onClose() {
closeBox();
}
protected:
void prepare() override;
void setInnerFocus() override;
@@ -38,13 +29,15 @@ protected:
private:
void updateMaxHeight();
void ratingChanged(int value);
void send();
void commentResized();
uint64 _callId = 0;
uint64 _callAccessHash = 0;
int _rating = 0;
std::vector<object_ptr<Ui::IconButton>> _stars;
object_ptr<Ui::InputArea> _comment = { nullptr };
object_ptr<Ui::InputField> _comment = { nullptr };
mtpRequestId _requestId = 0;

View File

@@ -14,23 +14,54 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/toast/toast.h"
#include "mainwindow.h"
ReportBox::ReportBox(QWidget*, PeerData *peer) : _peer(peer)
, _reasonGroup(std::make_shared<Ui::RadioenumGroup<Reason>>(Reason::Spam))
, _reasonSpam(this, _reasonGroup, Reason::Spam, lang(lng_report_reason_spam), st::defaultBoxCheckbox)
, _reasonViolence(this, _reasonGroup, Reason::Violence, lang(lng_report_reason_violence), st::defaultBoxCheckbox)
, _reasonPornography(this, _reasonGroup, Reason::Pornography, lang(lng_report_reason_pornography), st::defaultBoxCheckbox)
, _reasonOther(this, _reasonGroup, Reason::Other, lang(lng_report_reason_other), st::defaultBoxCheckbox) {
ReportBox::ReportBox(QWidget*, not_null<PeerData*> peer)
: _peer(peer) {
}
ReportBox::ReportBox(QWidget*, not_null<PeerData*> peer, MessageIdsList ids)
: _peer(peer)
, _ids(std::move(ids)) {
}
void ReportBox::prepare() {
setTitle(langFactory(_peer->isUser() ? lng_report_bot_title : (_peer->isMegagroup() ? lng_report_group_title : lng_report_title)));
setTitle(langFactory([&] {
if (_ids) {
return lng_report_message_title;
} else if (_peer->isUser()) {
return lng_report_bot_title;
} else if (_peer->isMegagroup()) {
return lng_report_group_title;
} else {
return lng_report_title;
}
}()));
addButton(langFactory(lng_report_button), [this] { onReport(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
addButton(langFactory(lng_report_button), [=] { report(); });
addButton(langFactory(lng_cancel), [=] { closeBox(); });
_reasonGroup->setChangedCallback([this](Reason value) { reasonChanged(value); });
_reasonGroup = std::make_shared<Ui::RadioenumGroup<Reason>>(
Reason::Spam);
const auto createButton = [&](
object_ptr<Ui::Radioenum<Reason>> &button,
Reason reason,
LangKey key) {
button.create(
this,
_reasonGroup,
reason,
lang(key),
st::defaultBoxCheckbox);
};
createButton(_reasonSpam, Reason::Spam, lng_report_reason_spam);
createButton(_reasonViolence, Reason::Violence, lng_report_reason_violence);
createButton(_reasonPornography, Reason::Pornography, lng_report_reason_pornography);
createButton(_reasonOther, Reason::Other, lng_report_reason_other);
_reasonGroup->setChangedCallback([=](Reason value) {
reasonChanged(value);
});
updateMaxHeight();
}
@@ -51,16 +82,20 @@ void ReportBox::resizeEvent(QResizeEvent *e) {
void ReportBox::reasonChanged(Reason reason) {
if (reason == Reason::Other) {
if (!_reasonOtherText) {
_reasonOtherText.create(this, st::profileReportReasonOther, langFactory(lng_report_reason_description));
_reasonOtherText.create(
this,
st::profileReportReasonOther,
Ui::InputField::Mode::MultiLine,
langFactory(lng_report_reason_description));
_reasonOtherText->show();
_reasonOtherText->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
_reasonOtherText->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
_reasonOtherText->setMaxLength(MaxPhotoCaption);
_reasonOtherText->resize(width() - (st::boxPadding.left() + st::boxOptionListPadding.left() + st::boxPadding.right()), _reasonOtherText->height());
updateMaxHeight();
connect(_reasonOtherText, SIGNAL(resized()), this, SLOT(onReasonResized()));
connect(_reasonOtherText, SIGNAL(submitted(bool)), this, SLOT(onReport()));
connect(_reasonOtherText, SIGNAL(cancelled()), this, SLOT(onClose()));
connect(_reasonOtherText, &Ui::InputField::resized, [=] { reasonResized(); });
connect(_reasonOtherText, &Ui::InputField::submitted, [=] { report(); });
connect(_reasonOtherText, &Ui::InputField::cancelled, [=] { closeBox(); });
}
_reasonOtherText->setFocusFast();
} else if (_reasonOtherText) {
@@ -77,12 +112,12 @@ void ReportBox::setInnerFocus() {
}
}
void ReportBox::onReasonResized() {
void ReportBox::reasonResized() {
updateMaxHeight();
update();
}
void ReportBox::onReport() {
void ReportBox::report() {
if (_requestId) return;
if (_reasonOtherText && _reasonOtherText->getLastText().trimmed().isEmpty()) {
@@ -90,7 +125,7 @@ void ReportBox::onReport() {
return;
}
auto getReason = [this]() {
const auto reason = [&] {
switch (_reasonGroup->value()) {
case Reason::Spam: return MTP_inputReportReasonSpam();
case Reason::Violence: return MTP_inputReportReasonViolence();
@@ -98,17 +133,37 @@ void ReportBox::onReport() {
case Reason::Other: return MTP_inputReportReasonOther(MTP_string(_reasonOtherText->getLastText()));
}
Unexpected("Bad reason group value.");
};
_requestId = MTP::send(MTPaccount_ReportPeer(_peer->input, getReason()), rpcDone(&ReportBox::reportDone), rpcFail(&ReportBox::reportFail));
}();
if (_ids) {
auto ids = QVector<MTPint>();
for (const auto &fullId : *_ids) {
ids.push_back(MTP_int(fullId.msg));
}
_requestId = MTP::send(
MTPmessages_Report(
_peer->input,
MTP_vector<MTPint>(ids),
reason),
rpcDone(&ReportBox::reportDone),
rpcFail(&ReportBox::reportFail));
} else {
_requestId = MTP::send(
MTPaccount_ReportPeer(_peer->input, reason),
rpcDone(&ReportBox::reportDone),
rpcFail(&ReportBox::reportFail));
}
}
void ReportBox::reportDone(const MTPBool &result) {
_requestId = 0;
Ui::show(Box<InformBox>(lang(lng_report_thanks)));
Ui::Toast::Show(lang(lng_report_thanks));
closeBox();
}
bool ReportBox::reportFail(const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
if (MTP::isDefaultHandledError(error)) {
return false;
}
_requestId = 0;
if (_reasonOtherText) {

View File

@@ -14,21 +14,13 @@ template <typename Enum>
class RadioenumGroup;
template <typename Enum>
class Radioenum;
class InputArea;
class InputField;
} // namespace Ui
class ReportBox : public BoxContent, public RPCSender {
Q_OBJECT
public:
ReportBox(QWidget*, PeerData *peer);
private slots:
void onReport();
void onReasonResized();
void onClose() {
closeBox();
}
ReportBox(QWidget*, not_null<PeerData*> peer);
ReportBox(QWidget*, not_null<PeerData*> peer, MessageIdsList ids);
protected:
void prepare() override;
@@ -44,19 +36,22 @@ private:
Other,
};
void reasonChanged(Reason reason);
void reasonResized();
void updateMaxHeight();
void report();
void reportDone(const MTPBool &result);
bool reportFail(const RPCError &error);
PeerData *_peer;
not_null<PeerData*> _peer;
base::optional<MessageIdsList> _ids;
std::shared_ptr<Ui::RadioenumGroup<Reason>> _reasonGroup;
object_ptr<Ui::Radioenum<Reason>> _reasonSpam;
object_ptr<Ui::Radioenum<Reason>> _reasonViolence;
object_ptr<Ui::Radioenum<Reason>> _reasonPornography;
object_ptr<Ui::Radioenum<Reason>> _reasonOther;
object_ptr<Ui::InputArea> _reasonOtherText = { nullptr };
object_ptr<Ui::Radioenum<Reason>> _reasonSpam = { nullptr };
object_ptr<Ui::Radioenum<Reason>> _reasonViolence = { nullptr };
object_ptr<Ui::Radioenum<Reason>> _reasonPornography = { nullptr };
object_ptr<Ui::Radioenum<Reason>> _reasonOther = { nullptr };
object_ptr<Ui::InputField> _reasonOtherText = { nullptr };
mtpRequestId _requestId = 0;

View File

@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_media_prepare.h"
#include "mainwidget.h"
#include "history/history_media_types.h"
#include "chat_helpers/message_field.h"
#include "core/file_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
@@ -1318,11 +1319,20 @@ void SendFilesBox::AlbumPreview::mouseReleaseEvent(QMouseEvent *e) {
SendFilesBox::SendFilesBox(
QWidget*,
not_null<Window::Controller*> controller,
Storage::PreparedList &&list,
const TextWithTags &caption,
CompressConfirm compressed)
: _list(std::move(list))
: _controller(controller)
, _list(std::move(list))
, _compressConfirmInitial(compressed)
, _compressConfirm(compressed) {
, _compressConfirm(compressed)
, _caption(
this,
st::confirmCaptionArea,
Ui::InputField::Mode::MultiLine,
FieldPlaceholder(_list),
caption) {
}
void SendFilesBox::initPreview(rpl::producer<int> desiredPreviewHeight) {
@@ -1417,6 +1427,7 @@ void SendFilesBox::prepare() {
_send = addButton(langFactory(lng_send_button), [this] { send(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
setupCaption();
initSendWay();
preparePreview();
subscribe(boxClosing, [this] {
@@ -1488,7 +1499,7 @@ void SendFilesBox::preparePreview() {
void SendFilesBox::setupControls() {
setupTitleText();
setupSendWayControls();
setupCaption();
_caption->setPlaceholder(FieldPlaceholder(_list));
}
void SendFilesBox::setupSendWayControls() {
@@ -1545,34 +1556,34 @@ void SendFilesBox::applyAlbumOrder() {
}
void SendFilesBox::setupCaption() {
if (_caption) {
_caption->setPlaceholder(FieldPlaceholder(_list));
return;
}
_caption.create(this, st::confirmCaptionArea, FieldPlaceholder(_list));
_caption->setMaxLength(MaxPhotoCaption);
_caption->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
connect(_caption, &Ui::InputArea::resized, this, [this] {
_caption->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
connect(_caption, &Ui::InputField::resized, [=] {
captionResized();
});
connect(_caption, &Ui::InputArea::submitted, this, [this](
bool ctrlShiftEnter) {
connect(_caption, &Ui::InputField::submitted, [=](
Qt::KeyboardModifiers modifiers) {
const auto ctrlShiftEnter = modifiers.testFlag(Qt::ShiftModifier)
&& (modifiers.testFlag(Qt::ControlModifier)
|| modifiers.testFlag(Qt::MetaModifier));
send(ctrlShiftEnter);
});
connect(_caption, &Ui::InputArea::cancelled, this, [this] {
closeBox();
});
_caption->setMimeDataHook([this](
connect(_caption, &Ui::InputField::cancelled, [=] { closeBox(); });
_caption->setMimeDataHook([=](
not_null<const QMimeData*> data,
Ui::InputArea::MimeAction action) {
if (action == Ui::InputArea::MimeAction::Check) {
Ui::InputField::MimeAction action) {
if (action == Ui::InputField::MimeAction::Check) {
return canAddFiles(data);
} else if (action == Ui::InputArea::MimeAction::Insert) {
} else if (action == Ui::InputField::MimeAction::Insert) {
return addFiles(data);
}
Unexpected("action in MimeData hook.");
});
_caption->setInstantReplaces(Ui::InstantReplaces::Default());
_caption->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
_caption->setMarkdownReplacesEnabled(rpl::single(true));
_caption->setEditLinkCallback(
DefaultEditLinkCallback(_controller, _caption));
}
void SendFilesBox::captionResized() {
@@ -1581,18 +1592,21 @@ void SendFilesBox::captionResized() {
update();
}
bool SendFilesBox::canAddUrls(const QList<QUrl> &urls) const {
return !urls.isEmpty() && ranges::find_if(
urls,
[](const QUrl &url) { return !url.isLocalFile(); }
) == urls.end();
}
bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
auto files = 0;
if (data->hasUrls()) {
for (const auto &url : data->urls()) {
if (url.isLocalFile()) {
++files;
}
}
} else if (data->hasImage()) {
++files;
const auto urls = data->hasUrls() ? data->urls() : QList<QUrl>();
auto filesCount = canAddUrls(urls) ? urls.size() : 0;
if (!filesCount && data->hasImage()) {
++filesCount;
}
if (_list.files.size() + files > Storage::MaxAlbumItems()) {
if (_list.files.size() + filesCount > Storage::MaxAlbumItems()) {
return false;
} else if (_list.files.size() > 1 && !_albumPreview) {
return false;
@@ -1605,10 +1619,14 @@ bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
auto list = [&] {
if (data->hasUrls()) {
return Storage::PrepareMediaList(
data->urls(),
st::sendMediaPreviewSize);
const auto urls = data->hasUrls() ? data->urls() : QList<QUrl>();
auto result = canAddUrls(urls)
? Storage::PrepareMediaList(urls, st::sendMediaPreviewSize)
: Storage::PreparedList(
Storage::PreparedList::Error::EmptyFile,
QString());
if (result.error == Storage::PreparedList::Error::None) {
return result;
} else if (data->hasImage()) {
auto image = qvariant_cast<QImage>(data->imageData());
if (!image.isNull()) {
@@ -1618,9 +1636,7 @@ bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
st::sendMediaPreviewSize);
}
}
return Storage::PreparedList(
Storage::PreparedList::Error::EmptyFile,
QString());
return result;
}();
if (_list.files.size() + list.files.size() > Storage::MaxAlbumItems()) {
return false;
@@ -1779,10 +1795,8 @@ void SendFilesBox::send(bool ctrlShiftEnter) {
_confirmed = true;
if (_confirmedCallback) {
auto caption = _caption
? TextUtilities::PrepareForSending(
_caption->getLastText(),
TextUtilities::PrepareTextOption::CheckLinks)
: QString();
? _caption->getTextWithAppliedMarkdown()
: TextWithTags();
_confirmedCallback(
std::move(_list),
way,

View File

@@ -12,13 +12,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localimageloader.h"
#include "storage/storage_media_prepare.h"
namespace Window {
class Controller;
} // namespace Window
namespace Ui {
template <typename Enum>
class Radioenum;
template <typename Enum>
class RadioenumGroup;
class RoundButton;
class InputArea;
class InputField;
struct GroupMediaLayout;
} // namespace Ui
@@ -32,14 +36,16 @@ class SendFilesBox : public BoxContent {
public:
SendFilesBox(
QWidget*,
not_null<Window::Controller*> controller,
Storage::PreparedList &&list,
const TextWithTags &caption,
CompressConfirm compressed);
void setConfirmedCallback(
base::lambda<void(
Storage::PreparedList &&list,
SendFilesWay way,
const QString &caption,
TextWithTags &&caption,
bool ctrlShiftEnter)> callback) {
_confirmedCallback = std::move(callback);
}
@@ -84,8 +90,11 @@ private:
void updateControlsGeometry();
bool canAddFiles(not_null<const QMimeData*> data) const;
bool canAddUrls(const QList<QUrl> &urls) const;
bool addFiles(not_null<const QMimeData*> data);
not_null<Window::Controller*> _controller;
QString _titleText;
int _titleHeight = 0;
@@ -97,12 +106,12 @@ private:
base::lambda<void(
Storage::PreparedList &&list,
SendFilesWay way,
const QString &caption,
TextWithTags &&caption,
bool ctrlShiftEnter)> _confirmedCallback;
base::lambda<void()> _cancelledCallback;
bool _confirmed = false;
object_ptr<Ui::InputArea> _caption = { nullptr };
object_ptr<Ui::InputField> _caption = { nullptr };
object_ptr<Ui::Radioenum<SendFilesWay>> _sendAlbum = { nullptr };
object_ptr<Ui::Radioenum<SendFilesWay>> _sendPhotos = { nullptr };
object_ptr<Ui::Radioenum<SendFilesWay>> _sendFiles = { nullptr };

View File

@@ -61,7 +61,14 @@ void ShareBox::prepare() {
}
});
_select->setResizedCallback([this] { updateScrollSkips(); });
_select->setSubmittedCallback([this](bool) { _inner->onSelectActive(); });
_select->setSubmittedCallback([this](Qt::KeyboardModifiers modifiers) {
if (modifiers.testFlag(Qt::ControlModifier)
|| modifiers.testFlag(Qt::MetaModifier)) {
onSubmit();
} else {
_inner->onSelectActive();
}
});
connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername()));
_inner->setPeerSelectedChangedCallback([this](PeerData *peer, bool checked) {
onPeerSelectedChanged(peer, checked);
@@ -573,15 +580,23 @@ void ShareBox::Inner::paintEvent(QPaintEvent *e) {
++indexFrom;
}
} else {
// empty
p.setFont(st::noContactsFont);
p.setPen(st::noContactsColor);
p.drawText(
rect().marginsRemoved(st::boxPadding),
lang(lng_bot_no_chats),
style::al_center);
}
} else {
if (_filtered.isEmpty() && _byUsernameFiltered.empty()) {
// empty
if (_filtered.isEmpty()
&& _byUsernameFiltered.empty()
&& !_searching) {
p.setFont(st::noContactsFont);
p.setPen(st::noContactsColor);
p.drawText(
rect().marginsRemoved(st::boxPadding),
lang(lng_bot_chats_not_found),
style::al_center);
} else {
auto filteredSize = _filtered.size();
if (filteredSize) {
@@ -781,7 +796,9 @@ void ShareBox::Inner::peopleReceived(
const QVector<MTPPeer> &my,
const QVector<MTPPeer> &people) {
_lastQuery = query.toLower().trimmed();
if (_lastQuery.at(0) == '@') _lastQuery = _lastQuery.mid(1);
if (_lastQuery.at(0) == '@') {
_lastQuery = _lastQuery.mid(1);
}
int32 already = _byUsernameFiltered.size();
_byUsernameFiltered.reserve(already + my.size() + people.size());
d_byUsernameFiltered.reserve(already + my.size() + people.size());

View File

@@ -610,14 +610,14 @@ StickersBox::Inner::Inner(QWidget *parent, not_null<ChannelData*> megagroup) : T
connect(
_megagroupSetField,
&Ui::MaskedInputField::changed,
[this] {
[=] {
_megagroupSetAddressChangedTimer.callOnce(
kHandleMegagroupSetAddressChangeTimeout);
});
connect(
_megagroupSetField,
&Ui::MaskedInputField::submitted,
[this] {
[=] {
_megagroupSetAddressChangedTimer.cancel();
handleMegagroupSetAddressChange();
});

View File

@@ -35,18 +35,18 @@ void UsernameBox::prepare() {
setTitle(langFactory(lng_username_title));
addButton(langFactory(lng_settings_save), [this] { onSave(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
addButton(langFactory(lng_settings_save), [=] { save(); });
addButton(langFactory(lng_cancel), [=] { closeBox(); });
connect(_username, SIGNAL(changed()), this, SLOT(onChanged()));
connect(_username, SIGNAL(submitted(bool)), this, SLOT(onSave()));
connect(_link, SIGNAL(clicked()), this, SLOT(onLinkClick()));
connect(_username, &Ui::MaskedInputField::changed, [=] { changed(); });
connect(_username, &Ui::MaskedInputField::submitted, [=] { save(); });
_link->addClickHandler([=] { linkClick(); });
_about.setRichText(st::usernameTextStyle, lang(lng_username_about));
setDimensions(st::boxWidth, st::usernamePadding.top() + _username->height() + st::usernameSkip + _about.countHeight(st::boxWidth - st::usernamePadding.left()) + 3 * st::usernameTextStyle.lineHeight + st::usernamePadding.bottom());
_checkTimer->setSingleShot(true);
connect(_checkTimer, SIGNAL(timeout()), this, SLOT(onCheck()));
connect(_checkTimer, &QTimer::timeout, [=] { check(); });
updateLinkText();
}
@@ -96,14 +96,14 @@ void UsernameBox::resizeEvent(QResizeEvent *e) {
_link->moveToLeft(st::usernamePadding.left(), linky + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2));
}
void UsernameBox::onSave() {
void UsernameBox::save() {
if (_saveRequestId) return;
_sentUsername = getName();
_saveRequestId = MTP::send(MTPaccount_UpdateUsername(MTP_string(_sentUsername)), rpcDone(&UsernameBox::onUpdateDone), rpcFail(&UsernameBox::onUpdateFail));
}
void UsernameBox::onCheck() {
void UsernameBox::check() {
if (_checkRequestId) {
MTP::cancel(_checkRequestId);
}
@@ -118,7 +118,7 @@ void UsernameBox::onCheck() {
}
}
void UsernameBox::onChanged() {
void UsernameBox::changed() {
updateLinkText();
QString name = getName();
if (name.isEmpty()) {
@@ -156,7 +156,7 @@ void UsernameBox::onChanged() {
}
}
void UsernameBox::onLinkClick() {
void UsernameBox::linkClick() {
Application::clipboard()->setText(Messenger::Instance().createInternalLinkFull(getName()));
Ui::Toast::Show(lang(lng_username_copied));
}

View File

@@ -15,8 +15,6 @@ class LinkButton;
} // namespace Ui
class UsernameBox : public BoxContent, public RPCSender {
Q_OBJECT
public:
UsernameBox(QWidget*);
@@ -27,14 +25,6 @@ protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private slots:
void onSave();
void onCheck();
void onChanged();
void onLinkClick();
private:
void onUpdateDone(const MTPUser &result);
bool onUpdateFail(const RPCError &error);
@@ -42,6 +32,13 @@ private:
void onCheckDone(const MTPBool &result);
bool onCheckFail(const RPCError &error);
void save();
void check();
void changed();
void linkClick();
QString getName() const;
void updateLinkText();

View File

@@ -10,6 +10,14 @@ using "basic.style";
using "ui/widgets/widgets.style";
using "window/window.style";
CallSignalBars {
width: pixels;
radius: pixels;
skip: pixels;
color: color;
inactiveOpacity: double;
}
callWidth: 300px;
callHeight: 470px;
callShadow: Shadow {
@@ -189,3 +197,16 @@ callDebugLabel: FlatLabel(defaultFlatLabel) {
margin: callDebugPadding;
}
callPanelDuration: 150;
callPanelSignalBars: CallSignalBars {
width: 3px;
radius: 1px;
skip: 1px;
color: callNameFg;
inactiveOpacity: 0.5;
}
callBarSignalBars: CallSignalBars(callPanelSignalBars) {
color: callBarFg;
}
callSignalMargin: 8px;
callSignalPadding: 4px;

View File

@@ -35,22 +35,35 @@ namespace Calls {
namespace {
constexpr auto kMinLayer = 65;
constexpr auto kMaxLayer = 65; // MTP::CurrentLayer?
constexpr auto kMaxLayer = 75;
constexpr auto kHangupTimeoutMs = 5000;
using tgvoip::Endpoint;
void ConvertEndpoint(std::vector<tgvoip::Endpoint> &ep, const MTPDphoneConnection &mtc) {
void ConvertEndpoint(
std::vector<tgvoip::Endpoint> &ep,
const MTPDphoneConnection &mtc) {
if (mtc.vpeer_tag.v.length() != 16) {
return;
}
auto ipv4 = tgvoip::IPv4Address(std::string(mtc.vip.v.constData(), mtc.vip.v.size()));
auto ipv6 = tgvoip::IPv6Address(std::string(mtc.vipv6.v.constData(), mtc.vipv6.v.size()));
ep.push_back(Endpoint((int64_t)mtc.vid.v, (uint16_t)mtc.vport.v, ipv4, ipv6, EP_TYPE_UDP_RELAY, (unsigned char*)mtc.vpeer_tag.v.data()));
auto ipv4 = tgvoip::IPv4Address(std::string(
mtc.vip.v.constData(),
mtc.vip.v.size()));
auto ipv6 = tgvoip::IPv6Address(std::string(
mtc.vipv6.v.constData(),
mtc.vipv6.v.size()));
ep.push_back(Endpoint(
(int64_t)mtc.vid.v,
(uint16_t)mtc.vport.v,
ipv4,
ipv6,
tgvoip::Endpoint::TYPE_UDP_RELAY,
(unsigned char*)mtc.vpeer_tag.v.data()));
}
constexpr auto kFingerprintDataSize = 256;
uint64 ComputeFingerprint(const std::array<gsl::byte, kFingerprintDataSize> &authKey) {
uint64 ComputeFingerprint(
const std::array<gsl::byte, kFingerprintDataSize> &authKey) {
auto hash = openssl::Sha1(authKey);
return (gsl::to_integer<uint64>(hash[19]) << 56)
| (gsl::to_integer<uint64>(hash[18]) << 48)
@@ -64,7 +77,50 @@ uint64 ComputeFingerprint(const std::array<gsl::byte, kFingerprintDataSize> &aut
} // namespace
Call::Call(not_null<Delegate*> delegate, not_null<UserData*> user, Type type)
void Call::ControllerPointer::create() {
Expects(_data == nullptr);
_data = std::make_unique<tgvoip::VoIPController>();
}
void Call::ControllerPointer::reset() {
if (const auto controller = base::take(_data)) {
controller->Stop();
}
}
bool Call::ControllerPointer::empty() const {
return (_data == nullptr);
}
bool Call::ControllerPointer::operator==(std::nullptr_t) const {
return empty();
}
Call::ControllerPointer::operator bool() const {
return !empty();
}
tgvoip::VoIPController *Call::ControllerPointer::operator->() const {
Expects(!empty());
return _data.get();
}
tgvoip::VoIPController &Call::ControllerPointer::operator*() const {
Expects(!empty());
return *_data;
}
Call::ControllerPointer::~ControllerPointer() {
reset();
}
Call::Call(
not_null<Delegate*> delegate,
not_null<UserData*> user,
Type type)
: _delegate(delegate)
, _user(user)
, _type(type) {
@@ -124,7 +180,16 @@ void Call::startOutgoing() {
Expects(_type == Type::Outgoing);
Expects(_state == State::Requesting);
request(MTPphone_RequestCall(_user->inputUser, MTP_int(rand_value<int32>()), MTP_bytes(_gaHash), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) {
request(MTPphone_RequestCall(
_user->inputUser,
MTP_int(rand_value<int32>()),
MTP_bytes(_gaHash),
MTP_phoneCallProtocol(
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
| MTPDphoneCallProtocol::Flag::f_udp_reflector),
MTP_int(kMinLayer),
MTP_int(kMaxLayer))
)).done([this](const MTPphone_PhoneCall &result) {
Expects(result.type() == mtpc_phone_phoneCall);
setState(State::Waiting);
@@ -185,7 +250,15 @@ void Call::answer() {
} else {
_answerAfterDhConfigReceived = false;
}
request(MTPphone_AcceptCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_bytes(_gb), _protocol)).done([this](const MTPphone_PhoneCall &result) {
request(MTPphone_AcceptCall(
MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)),
MTP_bytes(_gb),
MTP_phoneCallProtocol(
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
| MTPDphoneCallProtocol::Flag::f_udp_reflector),
MTP_int(kMinLayer),
MTP_int(kMaxLayer))
)).done([this](const MTPphone_PhoneCall &result) {
Expects(result.type() == mtpc_phone_phoneCall);
auto &call = result.c_phone_phoneCall();
App::feedUsers(call.vusers);
@@ -294,7 +367,6 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
}
_id = data.vid.v;
_accessHash = data.vaccess_hash.v;
_protocol = data.vprotocol;
auto gaHashBytes = bytesFromMTP(data.vg_a_hash);
if (gaHashBytes.size() != _gaHash.size()) {
LOG(("Call Error: Wrong g_a_hash size %1, expected %2.").arg(gaHashBytes.size()).arg(_gaHash.size()));
@@ -393,7 +465,16 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
_keyFingerprint = ComputeFingerprint(_authKey);
setState(State::ExchangingKeys);
request(MTPphone_ConfirmCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_bytes(_ga), MTP_long(_keyFingerprint), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) {
request(MTPphone_ConfirmCall(
MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)),
MTP_bytes(_ga),
MTP_long(_keyFingerprint),
MTP_phoneCallProtocol(
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
| MTPDphoneCallProtocol::Flag::f_udp_reflector),
MTP_int(kMinLayer),
MTP_int(kMaxLayer))
)).done([this](const MTPphone_PhoneCall &result) {
Expects(result.type() == mtpc_phone_phoneCall);
auto &call = result.c_phone_phoneCall();
App::feedUsers(call.vusers);
@@ -440,7 +521,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
}
voip_config_t config = { 0 };
config.data_saving = DATA_SAVING_NEVER;
config.data_saving = tgvoip::DATA_SAVING_NEVER;
#ifdef Q_OS_MAC
config.enableAEC = (QSysInfo::macVersion() < QSysInfo::MV_10_7);
#else // Q_OS_MAC
@@ -463,49 +544,77 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
}
}
std::vector<Endpoint> endpoints;
const auto &protocol = call.vprotocol.c_phoneCallProtocol();
auto endpoints = std::vector<Endpoint>();
ConvertEndpoint(endpoints, call.vconnection.c_phoneConnection());
for (int i = 0; i < call.valternative_connections.v.length(); i++) {
ConvertEndpoint(endpoints, call.valternative_connections.v[i].c_phoneConnection());
}
_controller = std::make_unique<tgvoip::VoIPController>();
auto callbacks = tgvoip::VoIPController::Callbacks();
callbacks.connectionStateChanged = [](
tgvoip::VoIPController *controller,
int state) {
const auto call = static_cast<Call*>(controller->implData);
call->handleControllerStateChange(controller, state);
};
callbacks.signalBarCountChanged = [](
tgvoip::VoIPController *controller,
int count) {
const auto call = static_cast<Call*>(controller->implData);
call->handleControllerBarCountChange(controller, count);
};
_controller.create();
if (_mute) {
_controller->SetMicMute(_mute);
}
_controller->implData = static_cast<void*>(this);
_controller->SetRemoteEndpoints(endpoints, true);
_controller->SetRemoteEndpoints(endpoints, true, protocol.vmax_layer.v);
_controller->SetConfig(&config);
_controller->SetEncryptionKey(reinterpret_cast<char*>(_authKey.data()), (_type == Type::Outgoing));
_controller->SetStateCallback([](tgvoip::VoIPController *controller, int state) {
static_cast<Call*>(controller->implData)->handleControllerStateChange(controller, state);
});
_controller->SetCallbacks(callbacks);
if (Global::UseProxy() && Global::UseProxyForCalls()) {
const auto proxy = Global::SelectedProxy();
if (proxy.supportsCalls()) {
Assert(proxy.type == ProxyData::Type::Socks5);
_controller->SetProxy(
tgvoip::PROXY_SOCKS5,
proxy.host.toStdString(),
proxy.port,
proxy.user.toStdString(),
proxy.password.toStdString());
}
}
_controller->Start();
_controller->Connect();
}
void Call::handleControllerStateChange(tgvoip::VoIPController *controller, int state) {
void Call::handleControllerStateChange(
tgvoip::VoIPController *controller,
int state) {
// NB! Can be called from an arbitrary thread!
// Expects(controller == _controller.get()); This can be called from ~VoIPController()!
// This can be called from ~VoIPController()!
// Expects(controller == _controller.get());
Expects(controller->implData == static_cast<void*>(this));
switch (state) {
case STATE_WAIT_INIT: {
case tgvoip::STATE_WAIT_INIT: {
DEBUG_LOG(("Call Info: State changed to WaitingInit."));
setStateQueued(State::WaitingInit);
} break;
case STATE_WAIT_INIT_ACK: {
case tgvoip::STATE_WAIT_INIT_ACK: {
DEBUG_LOG(("Call Info: State changed to WaitingInitAck."));
setStateQueued(State::WaitingInitAck);
} break;
case STATE_ESTABLISHED: {
case tgvoip::STATE_ESTABLISHED: {
DEBUG_LOG(("Call Info: State changed to Established."));
setStateQueued(State::Established);
} break;
case STATE_FAILED: {
case tgvoip::STATE_FAILED: {
auto error = controller->GetLastError();
LOG(("Call Info: State changed to Failed, error: %1.").arg(error));
setFailedQueued(error);
@@ -515,6 +624,26 @@ void Call::handleControllerStateChange(tgvoip::VoIPController *controller, int s
}
}
void Call::handleControllerBarCountChange(
tgvoip::VoIPController *controller,
int count) {
// NB! Can be called from an arbitrary thread!
// This can be called from ~VoIPController()!
// Expects(controller == _controller.get());
Expects(controller->implData == static_cast<void*>(this));
InvokeQueued(this, [=] {
setSignalBarCount(count);
});
}
void Call::setSignalBarCount(int count) {
if (_signalBarCount != count) {
_signalBarCount = count;
_signalBarCountChanged.notify(count);
}
}
template <typename T>
bool Call::checkCallCommonFields(const T &call) {
auto checkFailed = [this] {
@@ -608,6 +737,9 @@ void Call::setState(State state) {
void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
Expects(type != FinishType::None);
setSignalBarCount(kSignalBarFinished);
auto finalState = (type == FinishType::Ended) ? State::Ended : State::Failed;
auto hangupState = (type == FinishType::Ended) ? State::HangingUp : State::FailedHangingUp;
if (_state == State::Requesting) {
@@ -642,11 +774,15 @@ void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
}
void Call::setStateQueued(State state) {
InvokeQueued(this, [this, state] { setState(state); });
InvokeQueued(this, [=] {
setState(state);
});
}
void Call::setFailedQueued(int error) {
InvokeQueued(this, [this, error] { handleControllerError(error); });
InvokeQueued(this, [=] {
handleControllerError(error);
});
}
void Call::handleRequestError(const RPCError &error) {
@@ -661,9 +797,12 @@ void Call::handleRequestError(const RPCError &error) {
}
void Call::handleControllerError(int error) {
if (error == TGVOIP_ERROR_INCOMPATIBLE) {
Ui::show(Box<InformBox>(Lang::Hard::CallErrorIncompatible().replace("{user}", App::peerName(_user))));
} else if (error == TGVOIP_ERROR_AUDIO_IO) {
if (error == tgvoip::ERROR_INCOMPATIBLE) {
Ui::show(Box<InformBox>(
Lang::Hard::CallErrorIncompatible().replace(
"{user}",
App::peerName(_user))));
} else if (error == tgvoip::ERROR_AUDIO_IO) {
Ui::show(Box<InformBox>(lang(lng_call_error_audio_io)));
}
finish(FinishType::Failed);
@@ -675,6 +814,7 @@ void Call::destroyController() {
_controller.reset();
DEBUG_LOG(("Call Info: Call controller destroyed."));
}
setSignalBarCount(kSignalBarFinished);
}
Call::~Call() {

View File

@@ -93,6 +93,13 @@ public:
return _stateChanged;
}
static constexpr auto kSignalBarStarting = -1;
static constexpr auto kSignalBarFinished = -2;
static constexpr auto kSignalBarCount = 4;
base::Observable<int> &signalBarCountChanged() {
return _signalBarCountChanged;
}
void setMute(bool mute);
bool isMute() const {
return _mute;
@@ -116,6 +123,23 @@ public:
~Call();
private:
class ControllerPointer {
public:
void create();
void reset();
bool empty() const;
bool operator==(std::nullptr_t) const;
explicit operator bool() const;
tgvoip::VoIPController *operator->() const;
tgvoip::VoIPController &operator*() const;
~ControllerPointer();
private:
std::unique_ptr<tgvoip::VoIPController> _data;
};
enum class FinishType {
None,
Ended,
@@ -129,7 +153,12 @@ private:
void startWaitingTrack();
void generateModExpFirst(base::const_byte_span randomSeed);
void handleControllerStateChange(tgvoip::VoIPController *controller, int state);
void handleControllerStateChange(
tgvoip::VoIPController *controller,
int state);
void handleControllerBarCountChange(
tgvoip::VoIPController *controller,
int count);
void createAndStartController(const MTPDphoneCall &call);
template <typename T>
@@ -142,6 +171,7 @@ private:
void setState(State state);
void setStateQueued(State state);
void setFailedQueued(int error);
void setSignalBarCount(int count);
void destroyController();
not_null<Delegate*> _delegate;
@@ -151,6 +181,8 @@ private:
FinishType _finishAfterRequestingCall = FinishType::None;
bool _answerAfterDhConfigReceived = false;
base::Observable<State> _stateChanged;
int _signalBarCount = kSignalBarStarting;
base::Observable<int> _signalBarCountChanged;
TimeMs _startTime = 0;
base::DelayedCallTimer _finishByTimeoutTimer;
base::Timer _discardByTimeoutTimer;
@@ -170,7 +202,7 @@ private:
uint64 _accessHash = 0;
uint64 _keyFingerprint = 0;
std::unique_ptr<tgvoip::VoIPController> _controller;
ControllerPointer _controller;
std::unique_ptr<Media::Audio::Track> _waitingTrack;

View File

@@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Calls {
namespace {
ushort Data[] = {
const ushort Data[] = {
0xd83d, 0xde09, 0xd83d, 0xde0d, 0xd83d, 0xde1b, 0xd83d, 0xde2d, 0xd83d, 0xde31, 0xd83d, 0xde21,
0xd83d, 0xde0e, 0xd83d, 0xde34, 0xd83d, 0xde35, 0xd83d, 0xde08, 0xd83d, 0xde2c, 0xd83d, 0xde07,
0xd83d, 0xde0f, 0xd83d, 0xdc6e, 0xd83d, 0xdc77, 0xd83d, 0xdc82, 0xd83d, 0xdc76, 0xd83d, 0xdc68,
@@ -69,7 +69,7 @@ ushort Data[] = {
0x0030, 0x20e3, 0xd83d, 0xdd1f, 0x2757, 0x2753, 0x2665, 0x2666, 0xd83d, 0xdcaf, 0xd83d, 0xdd17,
0xd83d, 0xdd31, 0xd83d, 0xdd34, 0xd83d, 0xdd35, 0xd83d, 0xdd36, 0xd83d, 0xdd37 };
ushort Offsets[] = {
const ushort Offsets[] = {
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22,
24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46,
48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70,
@@ -119,7 +119,9 @@ std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
for (auto index = 0; index != EmojiCount; ++index) {
auto offset = Offsets[index];
auto size = Offsets[index + 1] - offset;
auto string = QString::fromRawData(reinterpret_cast<QChar*>(Data + offset), size);
auto string = QString::fromRawData(
reinterpret_cast<const QChar*>(Data + offset),
size);
auto emoji = Ui::Emoji::Find(string);
Assert(emoji != nullptr);
}
@@ -131,7 +133,9 @@ std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
auto index = value % EmojiCount;
auto offset = Offsets[index];
auto size = Offsets[index + 1] - offset;
auto string = QString::fromRawData(reinterpret_cast<QChar*>(Data + offset), size);
auto string = QString::fromRawData(
reinterpret_cast<const QChar*>(Data + offset),
size);
auto emoji = Ui::Emoji::Find(string);
Assert(emoji != nullptr);
result.push_back(emoji);

View File

@@ -67,6 +67,66 @@ private:
};
SignalBars::SignalBars(
QWidget *parent,
not_null<Call*> call,
const style::CallSignalBars &st,
base::lambda<void()> displayedChangedCallback)
: RpWidget(parent)
, _st(st)
, _displayedChangedCallback(std::move(displayedChangedCallback)) {
resize(
_st.width + (_st.width + _st.skip) * (Call::kSignalBarCount - 1),
_st.width * Call::kSignalBarCount);
subscribe(call->signalBarCountChanged(), [=](int count) {
changed(count);
});
}
bool SignalBars::isDisplayed() const {
return (_count >= 0);
}
void SignalBars::paintEvent(QPaintEvent *e) {
if (!isDisplayed()) {
return;
}
Painter p(this);
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(_st.color);
for (auto i = 0; i < Call::kSignalBarCount; ++i) {
p.setOpacity((i < _count) ? 1. : _st.inactiveOpacity);
const auto barHeight = (i + 1) * _st.width;
const auto barLeft = i * (_st.width + _st.skip);
const auto barTop = height() - barHeight;
p.drawRoundedRect(
barLeft,
barTop,
_st.width,
barHeight,
_st.radius,
_st.radius);
}
p.setOpacity(1.);
}
void SignalBars::changed(int count) {
if (_count == Call::kSignalBarFinished) {
return;
}
if (_count != count) {
const auto wasDisplayed = isDisplayed();
_count = count;
if (isDisplayed() != wasDisplayed && _displayedChangedCallback) {
_displayedChangedCallback();
}
update();
}
}
Panel::Button::Button(QWidget *parent, const style::CallButton &stFrom, const style::CallButton *stTo) : Ui::RippleButton(parent, stFrom.button.ripple)
, _stFrom(&stFrom)
, _stTo(stTo) {
@@ -238,7 +298,8 @@ Panel::Panel(not_null<Call*> call)
, _cancel(this, object_ptr<Button>(this, st::callCancel))
, _mute(this, st::callMuteToggle)
, _name(this, st::callName)
, _status(this, st::callStatus) {
, _status(this, st::callStatus)
, _signalBars(this, call, st::callPanelSignalBars) {
_decline->setDuration(st::callPanelDuration);
_cancel->setDuration(st::callPanelDuration);
@@ -337,10 +398,18 @@ void Panel::initControls() {
void Panel::reinitControls() {
Expects(_call != nullptr);
unsubscribe(_stateChangedSubscription);
_stateChangedSubscription = subscribe(_call->stateChanged(), [this](State state) { stateChanged(state); });
unsubscribe(base::take(_stateChangedSubscription));
_stateChangedSubscription = subscribe(
_call->stateChanged(),
[=](State state) { stateChanged(state); });
stateChanged(_call->state());
_signalBars.create(
this,
_call,
st::callPanelSignalBars,
[=] { rtlupdate(signalBarsRect()); });
_name->setText(App::peerName(_call->user()));
updateStatusText(_call->state());
}
@@ -585,6 +654,12 @@ void Panel::updateControlsGeometry() {
updateHangupGeometry();
_mute->moveToRight(_padding.right() + st::callMuteRight, controlsTop);
const auto skip = st::callSignalMargin + st::callSignalPadding;
const auto delta = (_signalBars->width() - _signalBars->height());
_signalBars->moveToLeft(
_padding.left() + skip,
_padding.top() + skip + delta / 2);
}
void Panel::updateHangupGeometry() {
@@ -637,6 +712,10 @@ void Panel::paintEvent(QPaintEvent *e) {
p.fillRect(0, _contentTop, width(), height() - _contentTop, brush);
}
if (_signalBars->isDisplayed()) {
paintSignalBarsBg(p);
}
if (!_fingerprint.empty()) {
App::roundRect(p, _fingerprintArea, st::callFingerprintBg, ImageRoundRadius::Small);
@@ -651,6 +730,23 @@ void Panel::paintEvent(QPaintEvent *e) {
}
}
QRect Panel::signalBarsRect() const {
const auto size = 2 * st::callSignalPadding + _signalBars->width();
return QRect(
_padding.left() + st::callSignalMargin,
_padding.top() + st::callSignalMargin,
size,
size);
}
void Panel::paintSignalBarsBg(Painter &p) {
App::roundRect(
p,
signalBarsRect(),
st::callFingerprintBg,
ImageRoundRadius::Small);
}
void Panel::closeEvent(QCloseEvent *e) {
if (_call) {
_call->hangup();

View File

@@ -20,8 +20,34 @@ template <typename Widget>
class FadeWrap;
} // namespace Ui
namespace style {
struct CallSignalBars;
} // namespace style
namespace Calls {
class SignalBars : public Ui::RpWidget, private base::Subscriber {
public:
SignalBars(
QWidget *parent,
not_null<Call*> call,
const style::CallSignalBars &st,
base::lambda<void()> displayedChangedCallback = nullptr);
bool isDisplayed() const;
protected:
void paintEvent(QPaintEvent *e) override;
private:
void changed(int count);
const style::CallSignalBars &_st;
int _count = Call::kSignalBarStarting;
base::lambda<void()> _displayedChangedCallback;
};
class Panel
: public Ui::RpWidget
, private base::Subscriber
@@ -67,6 +93,8 @@ private:
void refreshUserPhoto();
bool isGoodUserPhoto(PhotoData *photo);
void createUserpicCache(ImagePtr image);
QRect signalBarsRect() const;
void paintSignalBarsBg(Painter &p);
void updateControlsGeometry();
void updateHangupGeometry();
@@ -102,6 +130,7 @@ private:
object_ptr<Ui::IconButton> _mute;
object_ptr<Ui::FlatLabel> _name;
object_ptr<Ui::FlatLabel> _status;
object_ptr<SignalBars> _signalBars;
std::vector<EmojiPtr> _fingerprint;
QRect _fingerprintArea;

View File

@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "calls/calls_call.h"
#include "calls/calls_instance.h"
#include "calls/calls_panel.h"
#include "styles/style_boxes.h"
#include "observer_peer.h"
#include "boxes/abstract_box.h"
@@ -75,6 +76,7 @@ TopBar::TopBar(
: RpWidget(parent)
, _call(call)
, _durationLabel(this, st::callBarLabel)
, _signalBars(this, _call.get(), st::callBarSignalBars)
, _fullInfoLabel(this, st::callBarInfoLabel)
, _shortInfoLabel(this, st::callBarInfoLabel)
, _hangupLabel(this, st::callBarLabel, lang(lng_call_bar_hangup).toUpper())
@@ -169,14 +171,23 @@ void TopBar::resizeEvent(QResizeEvent *e) {
void TopBar::updateControlsGeometry() {
auto left = 0;
_mute->moveToLeft(left, 0); left += _mute->width();
_durationLabel->moveToLeft(left, st::callBarLabelTop); left += _durationLabel->width() + st::callBarSkip;
_mute->moveToLeft(left, 0);
left += _mute->width();
_durationLabel->moveToLeft(left, st::callBarLabelTop);
left += _durationLabel->width() + st::callBarSkip;
_signalBars->moveToLeft(left, (height() - _signalBars->height()) / 2);
left += _signalBars->width() + st::callBarSkip;
auto right = st::callBarRightSkip;
_hangupLabel->moveToRight(right, st::callBarLabelTop); right += _hangupLabel->width();
_hangupLabel->moveToRight(right, st::callBarLabelTop);
right += _hangupLabel->width();
right += st::callBarHangup.width;
_hangup->setGeometryToRight(0, 0, right, height());
_info->setGeometryToLeft(_mute->width(), 0, width() - _mute->width() - _hangup->width(), height());
_info->setGeometryToLeft(
_mute->width(),
0,
width() - _mute->width() - _hangup->width(),
height());
auto fullWidth = _fullInfoLabel->naturalWidth();
auto showFull = (left + fullWidth + right <= width());

View File

@@ -21,6 +21,7 @@ class FlatLabel;
namespace Calls {
class Call;
class SignalBars;
class TopBar : public Ui::RpWidget, private base::Subscriber {
public:
@@ -45,6 +46,7 @@ private:
bool _muted = false;
object_ptr<Ui::LabelSimple> _durationLabel;
object_ptr<SignalBars> _signalBars;
object_ptr<Ui::FlatLabel> _fullInfoLabel;
object_ptr<Ui::FlatLabel> _shortInfoLabel;
object_ptr<Ui::LabelSimple> _hangupLabel;

View File

@@ -14,11 +14,15 @@ namespace Ui {
namespace Emoji {
inline utf16string QStringToUTF16(const QString &string) {
return utf16string(reinterpret_cast<const utf16char*>(string.constData()), string.size());
return utf16string(
reinterpret_cast<const utf16char*>(string.constData()),
string.size());
}
inline QString QStringFromUTF16(utf16string string) {
return QString::fromRawData(reinterpret_cast<const QChar*>(string.data()), string.size());
return QString::fromRawData(
reinterpret_cast<const QChar*>(string.data()),
string.size());
}
constexpr auto kSuggestionMaxLength = internal::kReplacementMaxLength;

View File

@@ -346,12 +346,15 @@ void SuggestionsWidget::leaveEventHook(QEvent *e) {
return TWidget::leaveEventHook(e);
}
SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit*> field) : QObject(nullptr)
SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit*> field)
: QObject(nullptr)
, _field(field)
, _container(parent, st::emojiSuggestionsDropdown)
, _suggestions(_container->setOwnedWidget(object_ptr<Ui::Emoji::SuggestionsWidget>(parent, st::emojiSuggestionsMenu))) {
_container->setAutoHiding(false);
setReplaceCallback(nullptr);
_field->installEventFilter(this);
connect(_field, &QTextEdit::textChanged, this, [this] { handleTextChange(); });
connect(_field, &QTextEdit::cursorPositionChanged, this, [this] { handleCursorPositionChange(); });
@@ -363,6 +366,23 @@ SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit
handleTextChange();
}
void SuggestionsController::setReplaceCallback(
base::lambda<void(
int from,
int till,
const QString &replacement)> callback) {
if (callback) {
_replaceCallback = std::move(callback);
} else {
_replaceCallback = [=](int from, int till, const QString &replacement) {
auto cursor = _field->textCursor();
cursor.setPosition(from);
cursor.setPosition(till, QTextCursor::KeepAnchor);
cursor.insertText(replacement);
};
}
}
void SuggestionsController::handleTextChange() {
_ignoreCursorPositionChange = true;
InvokeQueued(this, [this] { _ignoreCursorPositionChange = false; });
@@ -374,16 +394,16 @@ void SuggestionsController::handleTextChange() {
}
QString SuggestionsController::getEmojiQuery() {
if (!cReplaceEmojis()) {
if (!Global::SuggestEmoji()) {
return QString();
}
auto cursor = _field->textCursor();
auto position = _field->textCursor().position();
if (cursor.anchor() != position) {
if (cursor.hasSelection()) {
return QString();
}
auto position = cursor.position();
auto findTextPart = [this, &position] {
auto document = _field->document();
auto block = document->findBlock(position);
@@ -411,10 +431,23 @@ QString SuggestionsController::getEmojiQuery() {
return QString();
}
auto isSuggestionChar = [](QChar ch) {
return (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '_') || (ch == '-') || (ch == '+');
const auto isUpperCaseLetter = [](QChar ch) {
return (ch >= 'A' && ch <= 'Z');
};
auto isGoodCharBeforeSuggestion = [isSuggestionChar](QChar ch) {
const auto isLetter = [](QChar ch) {
return (ch >= 'a' && ch <= 'z')
|| (ch >= 'A' && ch <= 'Z')
|| (ch >= '0' && ch <= '9');
};
const auto isSuggestionChar = [](QChar ch) {
return (ch >= 'a' && ch <= 'z')
|| (ch >= 'A' && ch <= 'Z')
|| (ch >= '0' && ch <= '9')
|| (ch == '_')
|| (ch == '-')
|| (ch == '+');
};
const auto isGoodCharBeforeSuggestion = [&](QChar ch) {
return !isSuggestionChar(ch) || (ch == 0);
};
Assert(position > 0 && position <= text.size());
@@ -427,7 +460,22 @@ QString SuggestionsController::getEmojiQuery() {
if (position > i + 1) {
// Skip colon and the first letter.
_queryStartPosition += i + 2;
return text.mid(i, position - i);
const auto length = position - i;
auto result = text.mid(i, length);
const auto upperCaseLetters = std::count_if(
result.begin(),
result.end(),
isUpperCaseLetter);
const auto letters = std::count_if(
result.begin(),
result.end(),
isLetter);
if (letters == upperCaseLetters && letters == 1) {
// No upper case single letter suggestions.
// We don't want to suggest emoji on :D and :-P
return QString();
}
return result.toLower();
}
}
return QString();
@@ -443,24 +491,14 @@ QString SuggestionsController::getEmojiQuery() {
}
void SuggestionsController::replaceCurrent(const QString &replacement) {
auto cursor = _field->textCursor();
auto suggestion = getEmojiQuery();
if (suggestion.isEmpty()) {
_suggestions->showWithQuery(QString());
} else {
cursor.setPosition(cursor.position() - suggestion.size(), QTextCursor::KeepAnchor);
cursor.insertText(replacement);
}
auto emojiText = GetSuggestionEmoji(QStringToUTF16(replacement));
if (auto emoji = Find(QStringFromUTF16(emojiText))) {
if (emoji->hasVariants()) {
auto it = cEmojiVariants().constFind(emoji->nonColoredId());
if (it != cEmojiVariants().cend()) {
emoji = emoji->variant(it.value());
}
}
AddRecent(emoji);
const auto cursor = _field->textCursor();
const auto position = cursor.position();
const auto from = position - suggestion.size();
_replaceCallback(from, position, replacement);
}
}

View File

@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
class InnerDropdown;
class FlatTextarea;
namespace Emoji {
@@ -69,6 +68,10 @@ public:
SuggestionsController(QWidget *parent, not_null<QTextEdit*> field);
void raise();
void setReplaceCallback(base::lambda<void(
int from,
int till,
const QString &replacement)> callback);
protected:
bool eventFilter(QObject *object, QEvent *event) override;
@@ -88,6 +91,10 @@ private:
bool _ignoreCursorPositionChange = false;
bool _textChangeAfterKeyPress = false;
QPointer<QTextEdit> _field;
base::lambda<void(
int from,
int till,
const QString &replacement)> _replaceCallback;
object_ptr<InnerDropdown> _container;
QPointer<SuggestionsWidget> _suggestions;

View File

@@ -523,10 +523,19 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
QRect r(e->rect());
if (r != rect()) p.setClipRect(r);
int32 atwidth = st::mentionFont->width('@'), hashwidth = st::mentionFont->width('#');
int32 mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize;
int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right();
int32 htagleft = st::historyAttach.width + st::historyComposeField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width;
auto atwidth = st::mentionFont->width('@');
auto hashwidth = st::mentionFont->width('#');
auto mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize;
auto mentionwidth = width()
- mentionleft
- 2 * st::mentionPadding.right();
auto htagleft = st::historyAttach.width
+ st::historyComposeField.textMargins.left()
- st::lineWidth;
auto htagwidth = width()
- st::mentionPadding.right()
- htagleft
- st::mentionScroll.width;
if (!_srows->empty()) {
int32 rows = rowscount(_srows->size(), _stickersPerRow);

View File

@@ -63,23 +63,23 @@ GifsListWidget::Footer::Footer(not_null<GifsListWidget*> parent) : InnerFooter(p
, _pan(parent)
, _field(this, st::gifsSearchField, langFactory(lng_gifs_search))
, _cancel(this, st::gifsSearchCancel) {
connect(_field, &Ui::InputField::submitted, this, [this](bool ctrlShiftEnter) {
connect(_field, &Ui::InputField::submitted, [=] {
_pan->sendInlineRequest();
});
connect(_field, &Ui::InputField::cancelled, this, [this] {
connect(_field, &Ui::InputField::cancelled, [=] {
if (_field->getLastText().isEmpty()) {
emit _pan->cancelled();
} else {
_field->setText(QString());
}
});
connect(_field, &Ui::InputField::changed, this, [this] {
connect(_field, &Ui::InputField::changed, [=] {
_cancel->toggle(
!_field->getLastText().isEmpty(),
anim::type::normal);
_pan->searchForGifs(_field->getLastText());
});
_cancel->setClickedCallback([this] {
_cancel->setClickedCallback([=] {
_field->setText(QString());
});
}

View File

@@ -9,24 +9,39 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_widget.h"
#include "base/qthelp_regex.h"
#include "styles/style_history.h"
#include "boxes/abstract_box.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_controller.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "auth_session.h"
#include "styles/style_boxes.h"
#include "styles/style_history.h"
namespace {
using EditLinkAction = Ui::InputField::EditLinkAction;
using EditLinkSelection = Ui::InputField::EditLinkSelection;
constexpr auto kParseLinksTimeout = TimeMs(1000);
const auto kMentionTagStart = qstr("mention://user.");
bool IsMentionLink(const QString &link) {
return link.startsWith(kMentionTagStart);
}
// For mention tags save and validate userId, ignore tags for different userId.
class FieldTagMimeProcessor : public Ui::FlatTextarea::TagMimeProcessor {
class FieldTagMimeProcessor : public Ui::InputField::TagMimeProcessor {
public:
QString mimeTagFromTag(const QString &tagId) override {
return ConvertTagToMimeTag(tagId);
}
QString tagFromMimeTag(const QString &mimeTag) override {
if (mimeTag.startsWith(qstr("mention://"))) {
if (IsMentionLink(mimeTag)) {
auto match = QRegularExpression(":(\\d+)$").match(mimeTag);
if (!match.hasMatch() || match.capturedRef(1).toInt() != Auth().userId()) {
if (!match.hasMatch()
|| match.capturedRef(1).toInt() != Auth().userId()) {
return QString();
}
return mimeTag.mid(0, mimeTag.size() - match.capturedLength());
@@ -36,15 +51,167 @@ public:
};
class EditLinkBox : public BoxContent {
public:
EditLinkBox(
QWidget*,
const QString &text,
const QString &link,
base::lambda<void(QString, QString)> callback);
void setInnerFocus() override;
protected:
void prepare() override;
private:
QString _startText;
QString _startLink;
base::lambda<void(QString, QString)> _callback;
base::lambda<void()> _setInnerFocus;
};
QRegularExpression RegExpProtocol() {
static const auto result = QRegularExpression("^([a-zA-Z]+)://");
return result;
}
bool IsGoodProtocol(const QString &protocol) {
const auto equals = [&](QLatin1String string) {
return protocol.compare(string, Qt::CaseInsensitive) == 0;
};
return equals(qstr("http"))
|| equals(qstr("https"))
|| equals(qstr("tg"));
}
QString NormalizeUrl(const QString &value) {
const auto trimmed = value.trimmed();
if (trimmed.isEmpty()) {
return QString();
}
const auto match = TextUtilities::RegExpDomainExplicit().match(trimmed);
if (!match.hasMatch()) {
const auto domain = TextUtilities::RegExpDomain().match(trimmed);
if (!domain.hasMatch() || domain.capturedStart() != 0) {
return QString();
}
return qstr("http://") + trimmed;
} else if (match.capturedStart() != 0) {
return QString();
}
const auto protocolMatch = RegExpProtocol().match(trimmed);
Assert(protocolMatch.hasMatch());
return IsGoodProtocol(protocolMatch.captured(1)) ? trimmed : QString();
}
//bool ValidateUrl(const QString &value) {
// const auto match = TextUtilities::RegExpDomain().match(value);
// if (!match.hasMatch() || match.capturedStart() != 0) {
// return false;
// }
// const auto protocolMatch = RegExpProtocol().match(value);
// return protocolMatch.hasMatch()
// && IsGoodProtocol(protocolMatch.captured(1));
//}
EditLinkBox::EditLinkBox(
QWidget*,
const QString &text,
const QString &link,
base::lambda<void(QString, QString)> callback)
: _startText(text)
, _startLink(link)
, _callback(std::move(callback)) {
Expects(_callback != nullptr);
}
void EditLinkBox::setInnerFocus() {
Expects(_setInnerFocus != nullptr);
_setInnerFocus();
}
void EditLinkBox::prepare() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
const auto text = content->add(
object_ptr<Ui::InputField>(
content,
st::defaultInputField,
langFactory(lng_formatting_link_text),
_startText),
st::markdownLinkFieldPadding);
text->setInstantReplaces(Ui::InstantReplaces::Default());
text->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
const auto url = content->add(
object_ptr<Ui::InputField>(
content,
st::defaultInputField,
langFactory(lng_formatting_link_url),
_startLink.trimmed()),
st::markdownLinkFieldPadding);
const auto submit = [=] {
const auto linkText = text->getLastText();
const auto linkUrl = NormalizeUrl(url->getLastText());
if (linkText.isEmpty()) {
text->showError();
return;
} else if (linkUrl.isEmpty()) {
url->showError();
return;
}
const auto weak = make_weak(this);
_callback(linkText, linkUrl);
if (weak) {
closeBox();
}
};
connect(text, &Ui::InputField::submitted, [=] {
url->setFocusFast();
});
connect(url, &Ui::InputField::submitted, [=] {
if (text->getLastText().isEmpty()) {
text->setFocusFast();
} else {
submit();
}
});
setTitle(langFactory(lng_formatting_link_create_title));
addButton(langFactory(lng_formatting_link_create), submit);
addButton(langFactory(lng_cancel), [=] { closeBox(); });
content->resizeToWidth(st::boxWidth);
content->moveToLeft(0, 0);
setDimensions(st::boxWidth, content->height());
_setInnerFocus = [=] {
(_startText.isEmpty() ? text : url)->setFocusFast();
};
}
} // namespace
QString ConvertTagToMimeTag(const QString &tagId) {
if (tagId.startsWith(qstr("mention://"))) {
if (IsMentionLink(tagId)) {
return tagId + ':' + QString::number(Auth().userId());
}
return tagId;
}
QString PrepareMentionTag(not_null<UserData*> user) {
return kMentionTagStart
+ QString::number(user->bareId())
+ '.'
+ QString::number(user->accessHash());
}
EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
EntitiesInText result;
if (tags.isEmpty()) {
@@ -52,12 +219,27 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
}
result.reserve(tags.size());
auto mentionStart = qstr("mention://user.");
for_const (auto &tag, tags) {
if (tag.id.startsWith(mentionStart)) {
if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(mentionStart.size()))) {
result.push_back(EntityInText(EntityInTextMentionName, tag.offset, tag.length, match->captured(1)));
for (const auto &tag : tags) {
const auto push = [&](
EntityInTextType type,
const QString &data = QString()) {
result.push_back(
EntityInText(type, tag.offset, tag.length, data));
};
if (IsMentionLink(tag.id)) {
if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(kMentionTagStart.size()))) {
push(EntityInTextMentionName, match->captured(1));
}
} else if (tag.id == Ui::InputField::kTagBold) {
push(EntityInTextBold);
} else if (tag.id == Ui::InputField::kTagItalic) {
push(EntityInTextItalic);
} else if (tag.id == Ui::InputField::kTagCode) {
push(EntityInTextCode);
} else if (tag.id == Ui::InputField::kTagPre) {
push(EntityInTextPre);
} else /*if (ValidateUrl(tag.id)) */{ // We validate when we insert.
push(EntityInTextCustomUrl, tag.id);
}
}
return result;
@@ -70,12 +252,28 @@ TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities) {
}
result.reserve(entities.size());
for_const (auto &entity, entities) {
if (entity.type() == EntityInTextMentionName) {
for (const auto &entity : entities) {
const auto push = [&](const QString &tag) {
result.push_back({ entity.offset(), entity.length(), tag });
};
switch (entity.type()) {
case EntityInTextMentionName: {
auto match = QRegularExpression("^(\\d+\\.\\d+)$").match(entity.data());
if (match.hasMatch()) {
result.push_back({ entity.offset(), entity.length(), qstr("mention://user.") + entity.data() });
push(kMentionTagStart + entity.data());
}
} break;
case EntityInTextCustomUrl: {
const auto url = entity.data();
if (Ui::InputField::IsValidMarkdownLink(url)
&& !IsMentionLink(url)) {
push(url);
}
} break;
case EntityInTextBold: push(Ui::InputField::kTagBold); break;
case EntityInTextItalic: push(Ui::InputField::kTagItalic); break;
case EntityInTextCode: push(Ui::InputField::kTagCode); break;
case EntityInTextPre: push(Ui::InputField::kTagPre); break;
}
}
return result;
@@ -95,8 +293,8 @@ std::unique_ptr<QMimeData> MimeDataFromTextWithEntities(
tag.id = ConvertTagToMimeTag(tag.id);
}
result->setData(
Ui::FlatTextarea::tagsMimeType(),
Ui::FlatTextarea::serializeTagsList(tags));
TextUtilities::TagsMimeType(),
TextUtilities::SerializeTags(tags));
}
return result;
}
@@ -109,59 +307,428 @@ void SetClipboardWithEntities(
}
}
MessageField::MessageField(QWidget *parent, not_null<Window::Controller*> controller, const style::FlatTextarea &st, base::lambda<QString()> placeholderFactory, const QString &val) : Ui::FlatTextarea(parent, st, std::move(placeholderFactory), val)
, _controller(controller) {
setMinHeight(st::historySendSize.height() - 2 * st::historySendPadding);
setMaxHeight(st::historyComposeFieldMaxHeight);
setTagMimeProcessor(std::make_unique<FieldTagMimeProcessor>());
base::lambda<bool(
Ui::InputField::EditLinkSelection selection,
QString text,
QString link,
EditLinkAction action)> DefaultEditLinkCallback(
not_null<Window::Controller*> controller,
not_null<Ui::InputField*> field) {
const auto weak = make_weak(field);
return [=](
EditLinkSelection selection,
QString text,
QString link,
EditLinkAction action) {
if (action == EditLinkAction::Check) {
return Ui::InputField::IsValidMarkdownLink(link)
&& !IsMentionLink(link);
}
Ui::show(Box<EditLinkBox>(text, link, [=](
const QString &text,
const QString &link) {
if (const auto strong = weak.data()) {
strong->commitMarkdownLinkEdit(selection, text, link);
}
}), LayerOption::KeepOther);
return true;
};
}
bool MessageField::hasSendText() const {
auto &text = getTextWithTags().text;
for (auto *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) {
auto code = ch->unicode();
if (code != ' ' && code != '\n' && code != '\r' && !chReplacedBySpace(code)) {
void InitMessageField(
not_null<Window::Controller*> controller,
not_null<Ui::InputField*> field) {
field->setMinHeight(st::historySendSize.height() - 2 * st::historySendPadding);
field->setMaxHeight(st::historyComposeFieldMaxHeight);
field->setTagMimeProcessor(std::make_unique<FieldTagMimeProcessor>());
field->document()->setDocumentMargin(4.);
field->setAdditionalMargin(convertScale(4) - 4);
field->customTab(true);
field->setInstantReplaces(Ui::InstantReplaces::Default());
field->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
field->setMarkdownReplacesEnabled(rpl::single(true));
field->setEditLinkCallback(
DefaultEditLinkCallback(controller, field));
}
bool HasSendText(not_null<const Ui::InputField*> field) {
const auto &text = field->getTextWithTags().text;
for (const auto ch : text) {
const auto code = ch.unicode();
if (code != ' '
&& code != '\n'
&& code != '\r'
&& !chReplacedBySpace(code)) {
return true;
}
}
return false;
}
void MessageField::onEmojiInsert(EmojiPtr emoji) {
if (isHidden()) return;
insertEmoji(emoji, textCursor());
}
InlineBotQuery ParseInlineBotQuery(not_null<const Ui::InputField*> field) {
auto result = InlineBotQuery();
void MessageField::dropEvent(QDropEvent *e) {
FlatTextarea::dropEvent(e);
if (e->isAccepted()) {
_controller->window()->activateWindow();
const auto &text = field->getTextWithTags().text;
const auto textLength = text.size();
auto inlineUsernameStart = 1;
auto inlineUsernameLength = 0;
if (textLength > 2 && text[0] == '@' && text[1].isLetter()) {
inlineUsernameLength = 1;
for (auto i = inlineUsernameStart + 1; i != textLength; ++i) {
const auto ch = text[i];
if (ch.isLetterOrNumber() || ch.unicode() == '_') {
++inlineUsernameLength;
continue;
} else if (!ch.isSpace()) {
inlineUsernameLength = 0;
}
break;
}
auto inlineUsernameEnd = inlineUsernameStart + inlineUsernameLength;
auto inlineUsernameEqualsText = (inlineUsernameEnd == textLength);
auto validInlineUsername = false;
if (inlineUsernameEqualsText) {
validInlineUsername = text.endsWith(qstr("bot"));
} else if (inlineUsernameEnd < textLength && inlineUsernameLength) {
validInlineUsername = text[inlineUsernameEnd].isSpace();
}
if (validInlineUsername) {
auto username = text.midRef(inlineUsernameStart, inlineUsernameLength);
if (username != result.username) {
result.username = username.toString();
if (const auto peer = App::peerByName(result.username)) {
if (const auto user = peer->asUser()) {
result.bot = peer->asUser();
} else {
result.bot = nullptr;
}
result.lookingUpBot = false;
} else {
result.bot = nullptr;
result.lookingUpBot = true;
}
}
if (result.lookingUpBot) {
result.query = QString();
return result;
} else if (result.bot && (!result.bot->botInfo
|| result.bot->botInfo->inlinePlaceholder.isEmpty())) {
result.bot = nullptr;
} else {
result.query = inlineUsernameEqualsText
? QString()
: text.mid(inlineUsernameEnd + 1);
return result;
}
} else {
inlineUsernameLength = 0;
}
}
if (inlineUsernameLength < 3) {
result.bot = nullptr;
result.username = QString();
}
result.query = QString();
return result;
}
bool MessageField::canInsertFromMimeData(const QMimeData *source) const {
if (source->hasUrls()) {
int32 files = 0;
for (int32 i = 0; i < source->urls().size(); ++i) {
if (source->urls().at(i).isLocalFile()) {
++files;
AutocompleteQuery ParseMentionHashtagBotCommandQuery(
not_null<const Ui::InputField*> field) {
auto result = AutocompleteQuery();
const auto cursor = field->textCursor();
if (cursor.hasSelection()) {
return result;
}
const auto position = cursor.position();
const auto document = field->document();
const auto block = document->findBlock(position);
for (auto item = block.begin(); !item.atEnd(); ++item) {
const auto fragment = item.fragment();
if (!fragment.isValid()) {
continue;
}
const auto fragmentPosition = fragment.position();
const auto fragmentEnd = fragmentPosition + fragment.length();
if (fragmentPosition >= position || fragmentEnd < position) {
continue;
}
const auto format = fragment.charFormat();
if (format.isImageFormat()) {
continue;
}
bool mentionInCommand = false;
const auto text = fragment.text();
for (auto i = position - fragmentPosition; i != 0; --i) {
if (text[i - 1] == '@') {
if ((position - fragmentPosition - i < 1 || text[i].isLetter()) && (i < 2 || !(text[i - 2].isLetterOrNumber() || text[i - 2] == '_'))) {
result.fromStart = (i == 1) && (fragmentPosition == 0);
result.query = text.mid(i - 1, position - fragmentPosition - i + 1);
} else if ((position - fragmentPosition - i < 1 || text[i].isLetter()) && i > 2 && (text[i - 2].isLetterOrNumber() || text[i - 2] == '_') && !mentionInCommand) {
mentionInCommand = true;
--i;
continue;
}
return result;
} else if (text[i - 1] == '#') {
if (i < 2 || !(text[i - 2].isLetterOrNumber() || text[i - 2] == '_')) {
result.fromStart = (i == 1) && (fragmentPosition == 0);
result.query = text.mid(i - 1, position - fragmentPosition - i + 1);
}
return result;
} else if (text[i - 1] == '/') {
if (i < 2) {
result.fromStart = (i == 1) && (fragmentPosition == 0);
result.query = text.mid(i - 1, position - fragmentPosition - i + 1);
}
return result;
}
if (position - fragmentPosition - i > 127 || (!mentionInCommand && (position - fragmentPosition - i > 63))) {
break;
}
if (!text[i - 1].isLetterOrNumber() && text[i - 1] != '_') {
break;
}
}
if (files > 1) return false; // multiple confirm with "compressed" checkbox
break;
}
if (source->hasImage()) return true;
return FlatTextarea::canInsertFromMimeData(source);
return result;
}
void MessageField::insertFromMimeData(const QMimeData *source) {
if (_insertFromMimeDataHook && _insertFromMimeDataHook(source)) {
QtConnectionOwner::QtConnectionOwner(QMetaObject::Connection connection)
: _data(connection) {
}
QtConnectionOwner::QtConnectionOwner(QtConnectionOwner &&other)
: _data(base::take(other._data)) {
}
QtConnectionOwner &QtConnectionOwner::operator=(QtConnectionOwner &&other) {
disconnect();
_data = base::take(other._data);
return *this;
}
void QtConnectionOwner::disconnect() {
QObject::disconnect(base::take(_data));
}
QtConnectionOwner::~QtConnectionOwner() {
disconnect();
}
MessageLinksParser::MessageLinksParser(not_null<Ui::InputField*> field)
: _field(field)
, _timer([=] { parse(); }) {
_connection = QObject::connect(_field, &Ui::InputField::changed, [=] {
const auto length = _field->getTextWithTags().text.size();
const auto timeout = (std::abs(length - _lastLength) > 2)
? 0
: kParseLinksTimeout;
if (!_timer.isActive() || timeout < _timer.remainingTime()) {
_timer.callOnce(timeout);
}
_lastLength = length;
});
_field->installEventFilter(this);
}
bool MessageLinksParser::eventFilter(QObject *object, QEvent *event) {
if (object == _field) {
if (event->type() == QEvent::KeyPress) {
const auto text = static_cast<QKeyEvent*>(event)->text();
if (!text.isEmpty() && text.size() < 3) {
const auto ch = text[0];
if (false
|| ch == '\n'
|| ch == '\r'
|| ch.isSpace()
|| ch == QChar::LineSeparator) {
_timer.callOnce(0);
}
}
} else if (event->type() == QEvent::Drop) {
_timer.callOnce(0);
}
}
return QObject::eventFilter(object, event);
}
const rpl::variable<QStringList> &MessageLinksParser::list() const {
return _list;
}
void MessageLinksParser::parse() {
const auto &textWithTags = _field->getTextWithTags();
const auto &text = textWithTags.text;
const auto &tags = textWithTags.tags;
const auto &markdownTags = _field->getMarkdownTags();
if (text.isEmpty()) {
_list = QStringList();
return;
}
FlatTextarea::insertFromMimeData(source);
auto ranges = QVector<LinkRange>();
auto tag = tags.begin();
const auto tagsEnd = tags.end();
const auto processTag = [&] {
Expects(tag != tagsEnd);
if (Ui::InputField::IsValidMarkdownLink(tag->id)
&& !IsMentionLink(tag->id)) {
ranges.push_back({ tag->offset, tag->length, tag->id });
}
++tag;
};
const auto processTagsBefore = [&](int offset) {
while (tag != tagsEnd && tag->offset + tag->length <= offset) {
processTag();
}
};
const auto hasTagsIntersection = [&](int till) {
if (tag == tagsEnd || tag->offset >= till) {
return false;
}
while (tag != tagsEnd && tag->offset < till) {
processTag();
}
return true;
};
auto markdownTag = markdownTags.begin();
const auto markdownTagsEnd = markdownTags.end();
const auto markdownTagsAllow = [&](int from, int length) {
while (markdownTag != markdownTagsEnd
&& (markdownTag->start + markdownTag->length <= from
|| !markdownTag->closed)) {
++markdownTag;
continue;
}
if (markdownTag == markdownTagsEnd
|| markdownTag->start >= from + length) {
return true;
}
// Ignore http-links that are completely inside some tags.
// This will allow sending http://test.com/__test__/test correctly.
return (markdownTag->start > from
|| markdownTag->start + markdownTag->length < from + length);
};
const auto len = text.size();
const QChar *start = text.unicode(), *end = start + text.size();
for (auto offset = 0, matchOffset = offset; offset < len;) {
auto m = TextUtilities::RegExpDomain().match(text, matchOffset);
if (!m.hasMatch()) break;
auto domainOffset = m.capturedStart();
auto protocol = m.captured(1).toLower();
auto topDomain = m.captured(3).toLower();
auto isProtocolValid = protocol.isEmpty() || TextUtilities::IsValidProtocol(protocol);
auto isTopDomainValid = !protocol.isEmpty() || TextUtilities::IsValidTopDomain(topDomain);
if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) {
auto forMailName = text.mid(offset, domainOffset - offset - 1);
auto mMailName = TextUtilities::RegExpMailNameAtEnd().match(forMailName);
if (mMailName.hasMatch()) {
offset = matchOffset = m.capturedEnd();
continue;
}
}
if (!isProtocolValid || !isTopDomainValid) {
offset = matchOffset = m.capturedEnd();
continue;
}
QStack<const QChar*> parenth;
const QChar *domainEnd = start + m.capturedEnd(), *p = domainEnd;
for (; p < end; ++p) {
QChar ch(*p);
if (chIsLinkEnd(ch)) break; // link finished
if (chIsAlmostLinkEnd(ch)) {
const QChar *endTest = p + 1;
while (endTest < end && chIsAlmostLinkEnd(*endTest)) {
++endTest;
}
if (endTest >= end || chIsLinkEnd(*endTest)) {
break; // link finished at p
}
p = endTest;
ch = *p;
}
if (ch == '(' || ch == '[' || ch == '{' || ch == '<') {
parenth.push(p);
} else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') {
if (parenth.isEmpty()) break;
const QChar *q = parenth.pop(), open(*q);
if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) {
p = q;
break;
}
}
}
if (p > domainEnd) { // check, that domain ended
if (domainEnd->unicode() != '/' && domainEnd->unicode() != '?') {
matchOffset = domainEnd - start;
continue;
}
}
const auto range = LinkRange {
domainOffset,
static_cast<int>(p - start - domainOffset),
QString()
};
processTagsBefore(domainOffset);
if (!hasTagsIntersection(range.start + range.length)) {
if (markdownTagsAllow(range.start, range.length)) {
ranges.push_back(range);
}
}
offset = matchOffset = p - start;
}
processTagsBefore(QFIXED_MAX);
apply(text, ranges);
}
void MessageField::focusInEvent(QFocusEvent *e) {
FlatTextarea::focusInEvent(e);
emit focused();
void MessageLinksParser::apply(
const QString &text,
const QVector<LinkRange> &ranges) {
const auto count = int(ranges.size());
const auto current = _list.current();
const auto computeLink = [&](const LinkRange &range) {
return range.custom.isEmpty()
? text.midRef(range.start, range.length)
: range.custom.midRef(0);
};
const auto changed = [&] {
if (current.size() != count) {
return true;
}
for (auto i = 0; i != count; ++i) {
if (computeLink(ranges[i]) != current[i]) {
return true;
}
}
return false;
}();
if (!changed) {
return;
}
auto parsed = QStringList();
parsed.reserve(count);
for (const auto &range : ranges) {
parsed.push_back(computeLink(range).toString());
}
_list = std::move(parsed);
}

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "ui/widgets/input_fields.h"
#include "base/timer.h"
class HistoryWidget;
namespace Window {
@@ -15,6 +16,7 @@ class Controller;
} // namespace Window
QString ConvertTagToMimeTag(const QString &tagId);
QString PrepareMentionTag(not_null<UserData*> user);
EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags);
TextWithTags::Tags ConvertEntitiesToTextTags(
@@ -25,32 +27,78 @@ void SetClipboardWithEntities(
const TextWithEntities &forClipboard,
QClipboard::Mode mode = QClipboard::Clipboard);
class MessageField final : public Ui::FlatTextarea {
Q_OBJECT
base::lambda<bool(
Ui::InputField::EditLinkSelection selection,
QString text,
QString link,
Ui::InputField::EditLinkAction action)> DefaultEditLinkCallback(
not_null<Window::Controller*> controller,
not_null<Ui::InputField*> field);
void InitMessageField(
not_null<Window::Controller*> controller,
not_null<Ui::InputField*> field);
bool HasSendText(not_null<const Ui::InputField*> field);
struct InlineBotQuery {
QString query;
QString username;
UserData *bot = nullptr;
bool lookingUpBot = false;
};
InlineBotQuery ParseInlineBotQuery(not_null<const Ui::InputField*> field);
struct AutocompleteQuery {
QString query;
bool fromStart = false;
};
AutocompleteQuery ParseMentionHashtagBotCommandQuery(
not_null<const Ui::InputField*> field);
class QtConnectionOwner {
public:
MessageField(QWidget *parent, not_null<Window::Controller*> controller, const style::FlatTextarea &st, base::lambda<QString()> placeholderFactory = base::lambda<QString()>(), const QString &val = QString());
bool hasSendText() const;
void setInsertFromMimeDataHook(base::lambda<bool(const QMimeData *data)> hook) {
_insertFromMimeDataHook = std::move(hook);
}
public slots:
void onEmojiInsert(EmojiPtr emoji);
signals:
void focused();
protected:
void focusInEvent(QFocusEvent *e) override;
void dropEvent(QDropEvent *e) override;
bool canInsertFromMimeData(const QMimeData *source) const override;
void insertFromMimeData(const QMimeData *source) override;
QtConnectionOwner(QMetaObject::Connection connection = {});
QtConnectionOwner(QtConnectionOwner &&other);
QtConnectionOwner &operator=(QtConnectionOwner &&other);
~QtConnectionOwner();
private:
not_null<Window::Controller*> _controller;
base::lambda<bool(const QMimeData *data)> _insertFromMimeDataHook;
void disconnect();
QMetaObject::Connection _data;
};
class MessageLinksParser : private QObject {
public:
MessageLinksParser(not_null<Ui::InputField*> field);
const rpl::variable<QStringList> &list() const;
protected:
bool eventFilter(QObject *object, QEvent *event) override;
private:
struct LinkRange {
int start;
int length;
QString custom;
};
friend inline bool operator==(const LinkRange &a, const LinkRange &b) {
return (a.start == b.start)
&& (a.length == b.length)
&& (a.custom == b.custom);
}
friend inline bool operator!=(const LinkRange &a, const LinkRange &b) {
return !(a == b);
}
void parse();
void apply(const QString &text, const QVector<LinkRange> &ranges);
not_null<Ui::InputField*> _field;
rpl::variable<QStringList> _list;
int _lastLength = 0;
base::Timer _timer;
QtConnectionOwner _connection;
};

View File

@@ -783,9 +783,9 @@ std::vector<not_null<DocumentData*>> GetListByEmoji(
addList(
Auth().data().stickerSetsOrder(),
MTPDstickerSet::Flag::f_archived);
addList(
Auth().data().featuredStickerSetsOrder(),
MTPDstickerSet::Flag::f_installed_date);
//addList(
// Auth().data().featuredStickerSetsOrder(),
// MTPDstickerSet::Flag::f_installed_date);
if (!setsToRequest.empty()) {
for (const auto [setId, accessHash] : setsToRequest) {
@@ -794,13 +794,15 @@ std::vector<not_null<DocumentData*>> GetListByEmoji(
Auth().api().requestStickerSets();
}
const auto others = Auth().api().stickersByEmoji(original);
if (!others) {
return {};
}
result.reserve(result.size() + others->size());
for (const auto document : *others) {
add(document, CreateOtherSortKey(document));
if (Global::SuggestStickersByEmoji()) {
const auto others = Auth().api().stickersByEmoji(original);
if (!others) {
return {};
}
result.reserve(result.size() + others->size());
for (const auto document : *others) {
add(document, CreateOtherSortKey(document));
}
}
ranges::action::sort(

View File

@@ -63,7 +63,9 @@ public:
Footer(not_null<StickersListWidget*> parent);
void preloadImages();
void validateSelectedIcon(uint64 setId, ValidateIconAnimations animations);
void validateSelectedIcon(
uint64 setId,
ValidateIconAnimations animations);
void refreshIcons(ValidateIconAnimations animations);
bool hasOnlyFeaturedSets() const;
@@ -99,6 +101,7 @@ private:
int newSelected,
ValidateIconAnimations animations);
void refreshIconsGeometry(ValidateIconAnimations animations);
void updateSelected();
void finishDragging();
void paintStickerSettingsIcon(Painter &p) const;
@@ -192,11 +195,11 @@ void StickersListWidget::Footer::initSearch() {
_searchField->setText(QString());
}
};
connect(_searchField, &Ui::InputField::submitted, this, [this](bool ctrlShiftEnter) {
connect(_searchField, &Ui::InputField::submitted, [=] {
_pan->sendSearchRequest();
});
connect(_searchField, &Ui::InputField::cancelled, this, cancelSearch);
connect(_searchField, &Ui::InputField::changed, this, [this] {
connect(_searchField, &Ui::InputField::cancelled, cancelSearch);
connect(_searchField, &Ui::InputField::changed, [=] {
_pan->searchForSets(_searchField->getLastText());
});
_searchCancel->setClickedCallback(cancelSearch);
@@ -253,7 +256,7 @@ void StickersListWidget::Footer::enumerateVisibleIcons(Callback callback) {
}
void StickersListWidget::Footer::preloadImages() {
enumerateVisibleIcons([this](const StickerIcon &icon, int x) {
enumerateVisibleIcons([](const StickerIcon &icon, int x) {
if (auto sticker = icon.sticker) {
sticker->thumb->load();
}
@@ -389,6 +392,7 @@ void StickersListWidget::Footer::resizeEvent(QResizeEvent *e) {
if (_searchField) {
resizeSearchControls();
}
refreshIconsGeometry(ValidateIconAnimations::None);
}
void StickersListWidget::Footer::resizeSearchControls() {
@@ -543,10 +547,11 @@ void StickersListWidget::Footer::updateSelected() {
if (y >= _iconsTop
&& y < _iconsTop + st::emojiFooterHeight
&& x >= _iconsLeft
&& x < width() - _iconsRight
&& x < _icons.size() * st::stickerIconWidth) {
&& x < width() - _iconsRight) {
x += qRound(_iconsX.current()) - _iconsLeft;
newOver = qFloor(x / st::stickerIconWidth);
if (x < _icons.size() * st::stickerIconWidth) {
newOver = qFloor(x / st::stickerIconWidth);
}
}
}
if (newOver != _iconOver) {
@@ -561,8 +566,13 @@ void StickersListWidget::Footer::updateSelected() {
void StickersListWidget::Footer::refreshIcons(
ValidateIconAnimations animations) {
_iconOver = SpecialOver::None;
_pan->fillIcons(_icons);
refreshIconsGeometry(animations);
}
void StickersListWidget::Footer::refreshIconsGeometry(
ValidateIconAnimations animations) {
_iconOver = _iconDown = SpecialOver::None;
_iconsX.finish();
_iconSelX.finish();
_iconsStartAnim = 0;
@@ -919,7 +929,7 @@ void StickersListWidget::refreshSearchRows(
_searchSets.clear();
fillLocalSearchRows(_searchNextQuery);
if (!cloudSets && _searchSets.empty()) {
if (!cloudSets && _searchNextQuery.isEmpty()) {
showStickerSet(!_mySets.empty()
? _mySets[0].id
: Stickers::FeaturedSetId);
@@ -1711,7 +1721,11 @@ void StickersListWidget::refreshSearchSets() {
void StickersListWidget::refreshSearchIndex() {
_searchIndex.clear();
for (const auto &set : _mySets) {
const auto list = TextUtilities::PrepareSearchWords(set.title + ' ' + set.shortName);
if (set.flags & MTPDstickerSet_ClientFlag::f_special) {
continue;
}
const auto string = set.title + ' ' + set.shortName;
const auto list = TextUtilities::PrepareSearchWords(string);
_searchIndex.emplace_back(set.id, list);
}
}

View File

@@ -40,7 +40,7 @@ Replace Replaces[] = {
// { { 0xD83DDE01U }, ":grin:" },
// { { 0xD83DDE02U }, ":joy:" },
{ { 0xD83DDE1AU }, ":-*" },
{ { 0xD83DDE06U }, "xD" },
// { { 0xD83DDE06U }, "xD" }, // Conflicts with typing xDDD...
// { { 0xD83DDC4DU }, ":like:" },
// { { 0xD83DDC4EU }, ":dislike:" },
// { { 0x261DU }, ":up:" },
@@ -57,7 +57,7 @@ Replace Replaces[] = {
{ { 0xD83DDE22U }, ":'(" },
{ { 0xD83DDE2DU }, ":_(" },
{ { 0xD83DDE29U }, ":((" },
{ { 0xD83DDE28U }, ":o" },
// { { 0xD83DDE28U }, ":o" }, // Conflicts with typing :ok...
{ { 0xD83DDE10U }, ":|" },
{ { 0xD83DDE0CU }, "3-)" },
{ { 0xD83DDE20U }, ">(" },

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