Compare commits
183 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37cf12f06e | ||
|
|
fa4b538e6f | ||
|
|
0495cf4187 | ||
|
|
933f1944c7 | ||
|
|
925849858b | ||
|
|
3c3829d9c5 | ||
|
|
88f3aeca5b | ||
|
|
c7b3c95dc6 | ||
|
|
e950130da6 | ||
|
|
a4cdd83816 | ||
|
|
e10964a0bc | ||
|
|
42a2f53a11 | ||
|
|
f713585f17 | ||
|
|
5278ed1f41 | ||
|
|
58cedb796e | ||
|
|
de11987312 | ||
|
|
204cfaa8ca | ||
|
|
5d20d585b3 | ||
|
|
2be4641496 | ||
|
|
b62e1d5036 | ||
|
|
fc8d1e21e8 | ||
|
|
8ddbfb7de5 | ||
|
|
89687e5bff | ||
|
|
fcfacf1f9d | ||
|
|
a6484e6131 | ||
|
|
c2578f9a5a | ||
|
|
0de77a051a | ||
|
|
b0f8846d12 | ||
|
|
6258aa01b8 | ||
|
|
c8aa97b6b2 | ||
|
|
d67c48fda1 | ||
|
|
7caabb8f5a | ||
|
|
37454b4ff4 | ||
|
|
cd032f5c16 | ||
|
|
0c17bdc783 | ||
|
|
23a1f7b83c | ||
|
|
465a33f095 | ||
|
|
8820b9046d | ||
|
|
09bd953c18 | ||
|
|
6f89413c76 | ||
|
|
a65e25b8ae | ||
|
|
24ecd2ac88 | ||
|
|
e8f27be364 | ||
|
|
5e12cd27df | ||
|
|
7d1cc67019 | ||
|
|
507a064153 | ||
|
|
9751d36788 | ||
|
|
c46fd66abe | ||
|
|
ec3957fcf3 | ||
|
|
b80b770631 | ||
|
|
0f234188e1 | ||
|
|
df5baba86b | ||
|
|
64bd839d2c | ||
|
|
9390450049 | ||
|
|
e3334f7a87 | ||
|
|
58ed30d30e | ||
|
|
cdfdccbb66 | ||
|
|
6b8f80bd63 | ||
|
|
6e5dfc79d4 | ||
|
|
f8e76f1b84 | ||
|
|
2c75fe033c | ||
|
|
b3667d69a1 | ||
|
|
ba520aadcb | ||
|
|
e4c16ccba4 | ||
|
|
3e332ad8e7 | ||
|
|
5154fe0044 | ||
|
|
dcb1315d53 | ||
|
|
7023b013ce | ||
|
|
86ed2745e3 | ||
|
|
7db2acc742 | ||
|
|
745b01a407 | ||
|
|
95979b1ad9 | ||
|
|
5910efa0bd | ||
|
|
037e8f1858 | ||
|
|
b1d1d73541 | ||
|
|
9b154b3c91 | ||
|
|
a1f9b5a96f | ||
|
|
2887c0b564 | ||
|
|
9b7826ea0d | ||
|
|
241be89e5c | ||
|
|
39075538fb | ||
|
|
1592f70a7c | ||
|
|
732bb25666 | ||
|
|
5cba1cdc64 | ||
|
|
d346925b9d | ||
|
|
02f3985125 | ||
|
|
f3db43abc9 | ||
|
|
ecf61712cd | ||
|
|
b47c66155d | ||
|
|
2fda96a375 | ||
|
|
12c2e42917 | ||
|
|
94a956ce19 | ||
|
|
704f64a0c9 | ||
|
|
f9ca7f4505 | ||
|
|
0a3d31a91f | ||
|
|
3c17fab15a | ||
|
|
2efe409c60 | ||
|
|
1176421bf2 | ||
|
|
05911a7172 | ||
|
|
1326359745 | ||
|
|
fc26457218 | ||
|
|
173108a9cb | ||
|
|
7307f0b1a5 | ||
|
|
c49dac57b7 | ||
|
|
6288da2f3d | ||
|
|
ce37c6ef08 | ||
|
|
5f93725431 | ||
|
|
90dfdb0e1f | ||
|
|
7cd330db9a | ||
|
|
b14ac5cafe | ||
|
|
1fc929b78f | ||
|
|
fd47fd4d9e | ||
|
|
9b74958fab | ||
|
|
7091fb9448 | ||
|
|
876cdcf26a | ||
|
|
36eca970f2 | ||
|
|
21232e09a4 | ||
|
|
2d9d373c7f | ||
|
|
23387d6625 | ||
|
|
6137c64444 | ||
|
|
43a830f0af | ||
|
|
ff331c040a | ||
|
|
3532e187fd | ||
|
|
6467ba7739 | ||
|
|
8de3b2c0d3 | ||
|
|
deeb022e0b | ||
|
|
9e0e28dc45 | ||
|
|
c99ac0a264 | ||
|
|
991fafb30e | ||
|
|
5cf5d4b4c4 | ||
|
|
38e42f9a95 | ||
|
|
9b7689993f | ||
|
|
0e3eddcb77 | ||
|
|
3f829ef3b9 | ||
|
|
de8d93ba73 | ||
|
|
dad9f4b87d | ||
|
|
0f538e2606 | ||
|
|
08fa6a9815 | ||
|
|
a7cf4027ea | ||
|
|
646c7ecceb | ||
|
|
3cbbe3d3c2 | ||
|
|
0af26dd353 | ||
|
|
159e366122 | ||
|
|
b9081c26ba | ||
|
|
9933c6ba59 | ||
|
|
eb0642f569 | ||
|
|
1cce35a5a5 | ||
|
|
aeb71e089a | ||
|
|
b962efeca3 | ||
|
|
eb6c350e72 | ||
|
|
d496d41e7e | ||
|
|
19aa4f4acc | ||
|
|
19350e3846 | ||
|
|
741b524d71 | ||
|
|
84288112fc | ||
|
|
7c537cd787 | ||
|
|
c56977cbc1 | ||
|
|
2afa2cd9ab | ||
|
|
442d0da5c1 | ||
|
|
db6bdf36af | ||
|
|
b246328dcf | ||
|
|
a27ea35edd | ||
|
|
a7c4aea9ff | ||
|
|
1ba870a655 | ||
|
|
5bc3cf56fd | ||
|
|
3c4cf2862b | ||
|
|
af69a7a01f | ||
|
|
b9f7a501f5 | ||
|
|
322a085b70 | ||
|
|
6c4dc34441 | ||
|
|
efa287b786 | ||
|
|
23e1c6128b | ||
|
|
bc71a2619a | ||
|
|
4f3510c47c | ||
|
|
2adc20f07f | ||
|
|
b6ade7ce19 | ||
|
|
cabed9587b | ||
|
|
0ce01410a1 | ||
|
|
d02819db13 | ||
|
|
46bae9ed74 | ||
|
|
693ff3398e | ||
|
|
567216f41f | ||
|
|
1ef0791bc6 |
@@ -56,11 +56,11 @@ include(cmake/options.cmake)
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
if (WIN32)
|
||||
set(qt_version 5.15.7)
|
||||
set(qt_version 5.15.8)
|
||||
elseif (APPLE)
|
||||
set(qt_version 6.3.2)
|
||||
else()
|
||||
set(qt_version 6.4.1)
|
||||
set(qt_version 6.4.2)
|
||||
endif()
|
||||
endif()
|
||||
include(cmake/external/qt/package.cmake)
|
||||
|
||||
2
LEGAL
2
LEGAL
@@ -1,7 +1,7 @@
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
Copyright (c) 2014-2022 The Telegram Desktop Authors.
|
||||
Copyright (c) 2014-2023 The Telegram Desktop Authors.
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -243,8 +243,6 @@ PRIVATE
|
||||
boxes/download_path_box.h
|
||||
boxes/edit_caption_box.cpp
|
||||
boxes/edit_caption_box.h
|
||||
boxes/edit_color_box.cpp
|
||||
boxes/edit_color_box.h
|
||||
boxes/edit_privacy_box.cpp
|
||||
boxes/edit_privacy_box.h
|
||||
boxes/gift_premium_box.cpp
|
||||
@@ -841,8 +839,6 @@ PRIVATE
|
||||
info/profile/info_profile_cover.h
|
||||
info/profile/info_profile_emoji_status_panel.cpp
|
||||
info/profile/info_profile_emoji_status_panel.h
|
||||
info/profile/info_profile_icon.cpp
|
||||
info/profile/info_profile_icon.h
|
||||
info/profile/info_profile_inner_widget.cpp
|
||||
info/profile/info_profile_inner_widget.h
|
||||
info/profile/info_profile_members.cpp
|
||||
@@ -1139,7 +1135,6 @@ PRIVATE
|
||||
platform/platform_integration.h
|
||||
platform/platform_main_window.h
|
||||
platform/platform_notifications_manager.h
|
||||
platform/platform_specific.cpp
|
||||
platform/platform_specific.h
|
||||
platform/platform_tray.h
|
||||
platform/platform_window_title.h
|
||||
@@ -1201,6 +1196,8 @@ PRIVATE
|
||||
settings/settings_privacy_controllers.h
|
||||
settings/settings_privacy_security.cpp
|
||||
settings/settings_privacy_security.h
|
||||
settings/settings_scale_preview.cpp
|
||||
settings/settings_scale_preview.h
|
||||
settings/settings_type.h
|
||||
storage/details/storage_file_utilities.cpp
|
||||
storage/details/storage_file_utilities.h
|
||||
|
||||
BIN
Telegram/Resources/animations/discussion.tgs
Normal file
BIN
Telegram/Resources/animations/discussion.tgs
Normal file
Binary file not shown.
@@ -390,6 +390,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_no_chats_filter" = "No chats currently belong to this folder.";
|
||||
"lng_contacts_loading" = "Loading...";
|
||||
"lng_contacts_not_found" = "No contacts found";
|
||||
"lng_topics_not_found" = "No topics found.";
|
||||
"lng_dlg_search_for_messages" = "Search for messages";
|
||||
"lng_update_telegram" = "Update Telegram";
|
||||
"lng_dlg_search_in" = "Search messages in";
|
||||
@@ -755,7 +756,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_download_path" = "Download path";
|
||||
"lng_download_path_temp" = "Temp folder";
|
||||
"lng_download_path_default" = "Default folder";
|
||||
"lng_download_path_unset" = "Unset";
|
||||
"lng_download_path_clear" = "Clear all";
|
||||
"lng_download_path_header" = "Choose download path";
|
||||
"lng_download_path_default_radio" = "Telegram folder in system «Downloads»";
|
||||
@@ -763,7 +763,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_download_path_dir_radio" = "Custom folder, cleared only manually";
|
||||
"lng_download_path_choose" = "Choose download path";
|
||||
"lng_sure_clear_downloads" = "Do you want to remove all downloaded files from temp folder? It is done automatically on logout or program uninstall.";
|
||||
"lng_download_path_failed" = "File download could not be started.\n\nThis might be because the download location you've selected is invalid. Try changing the \"Download path\" in Settings.";
|
||||
"lng_download_path_failed" = "File download could not be started.\n\nThe default download location will be used now. You can always change it in Settings > Advanced > Download Path.\n\nPlease try once again.";
|
||||
"lng_download_path_settings" = "Settings";
|
||||
"lng_download_finish_failed" = "File download could not be finished.\n\nWould you like to try again?";
|
||||
"lng_download_path_clearing" = "Clearing...";
|
||||
@@ -1332,8 +1332,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_discussion_group_add" = "Add a group";
|
||||
"lng_manage_linked_channel" = "Linked channel";
|
||||
"lng_manage_linked_channel_restore" = "Restore linked channel";
|
||||
"lng_manage_discussion_group_about" = "Select a group chat for discussion that will be displayed in your channel.";
|
||||
"lng_manage_discussion_group_about_chosen" = "A link to {group} is shown to all subscribers in the bottom panel.";
|
||||
"lng_manage_discussion_group_about" = "Select a group chat that will host comments from your channel.";
|
||||
"lng_manage_discussion_group_about_chosen" = "{group} is selected as the group that hosts comments for your channel.";
|
||||
"lng_manage_discussion_group_create" = "Create a new group";
|
||||
"lng_manage_discussion_group_unlink" = "Unlink group";
|
||||
"lng_manage_discussion_group_posted" = "Everything you post in the channel is forwarded to this group.";
|
||||
@@ -1529,6 +1529,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_ttl_changed_you" = "You set messages to auto-delete in {duration}";
|
||||
"lng_action_ttl_changed_channel" = "New messages will auto-delete in {duration}";
|
||||
"lng_action_ttl_global" = "{from} uses a self-destruct timer for all chats. All new messages in this chat will be automatically deleted after {duration} they've been sent.";
|
||||
"lng_action_ttl_global_me" = "You set a self-destruct timer for all chats. All new messages in this chat will be automatically deleted after {duration} they’ve been sent.";
|
||||
"lng_action_ttl_removed" = "{from} has set messages not to auto-delete";
|
||||
"lng_action_ttl_removed_you" = "You disabled the auto-delete timer";
|
||||
"lng_action_ttl_removed_channel" = "New messages will not auto-delete";
|
||||
@@ -2498,6 +2499,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_mediaview_channel_photo" = "Channel Photo";
|
||||
"lng_mediaview_profile_photo" = "Profile Photo";
|
||||
"lng_mediaview_profile_public_photo" = "Public Photo";
|
||||
"lng_mediaview_profile_photo_by_you" = "Photo set by you";
|
||||
"lng_mediaview_file_n_of_amount" = "{file} {n} of {amount}";
|
||||
"lng_mediaview_n_of_amount" = "Photo {n} of {amount}";
|
||||
"lng_mediaview_doc_image" = "File";
|
||||
@@ -2692,6 +2694,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_call_box_clear_all" = "Clear All";
|
||||
"lng_call_box_clear_sure" = "Are you sure you want to completely clear your calls log?";
|
||||
"lng_call_box_clear_button" = "Clear";
|
||||
"lng_call_box_groupcalls_subtitle" = "Active video chats";
|
||||
|
||||
"lng_call_outgoing" = "Outgoing call";
|
||||
"lng_call_video_outgoing" = "Outgoing video call";
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
<file alias="cloud_password/hint.tgs">../../animations/cloud_password/hint.tgs</file>
|
||||
<file alias="cloud_password/email.tgs">../../animations/cloud_password/email.tgs</file>
|
||||
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
|
||||
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.4.3.0" />
|
||||
Version="4.5.9.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -14,4 +14,9 @@
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,4,3,0
|
||||
PRODUCTVERSION 4,4,3,0
|
||||
FILEVERSION 4,5,9,0
|
||||
PRODUCTVERSION 4,5,9,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "4.4.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "FileVersion", "4.5.9.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.4.3.0"
|
||||
VALUE "ProductVersion", "4.5.9.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,4,3,0
|
||||
PRODUCTVERSION 4,4,3,0
|
||||
FILEVERSION 4,5,9,0
|
||||
PRODUCTVERSION 4,5,9,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "4.4.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "FileVersion", "4.5.9.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.4.3.0"
|
||||
VALUE "ProductVersion", "4.5.9.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -148,6 +148,7 @@ void EditMessageWithUploadedMedia(
|
||||
MTPInputMedia media) {
|
||||
const auto done = [=](Fn<void()> applyUpdates) {
|
||||
if (item) {
|
||||
item->removeFromSharedMediaIndex();
|
||||
item->clearSavedMedia();
|
||||
item->setIsLocalUpdateMedia(true);
|
||||
applyUpdates();
|
||||
|
||||
@@ -21,6 +21,9 @@ namespace Api {
|
||||
template<typename Option>
|
||||
[[nodiscard]] Data::SubscriptionOptions SubscriptionOptionsFromTL(
|
||||
const QVector<Option> &tlOptions) {
|
||||
if (tlOptions.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
auto result = Data::SubscriptionOptions();
|
||||
const auto monthlyAmount = [&] {
|
||||
const auto &min = ranges::min_element(
|
||||
|
||||
@@ -36,7 +36,7 @@ bool UnreadThings::trackMentions(Data::Thread *thread) const {
|
||||
|
||||
bool UnreadThings::trackReactions(Data::Thread *thread) const {
|
||||
const auto peer = thread ? thread->peer().get() : nullptr;
|
||||
return peer && (peer->isChat() || peer->isMegagroup());
|
||||
return peer && (peer->isUser() || peer->isChat() || peer->isMegagroup());
|
||||
}
|
||||
|
||||
void UnreadThings::preloadEnough(Data::Thread *thread) {
|
||||
|
||||
@@ -123,22 +123,18 @@ using UpdatedFileReferences = Data::UpdatedFileReferences;
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Window::Show> ShowForPeer(
|
||||
not_null<PeerData*> peer) {
|
||||
const auto separate = Core::App().separateWindowForPeer(peer);
|
||||
const auto window = separate ? separate : Core::App().primaryWindow();
|
||||
return std::make_shared<Window::Show>(window);
|
||||
return std::make_shared<Window::Show>(Core::App().windowFor(peer));
|
||||
}
|
||||
|
||||
void ShowChannelsLimitBox(not_null<PeerData*> peer) {
|
||||
const auto primary = Core::App().primaryWindow();
|
||||
if (!primary) {
|
||||
return;
|
||||
if (const auto window = Core::App().windowFor(peer)) {
|
||||
window->invokeForSessionController(
|
||||
&peer->session().account(),
|
||||
peer,
|
||||
[&](not_null<Window::SessionController*> controller) {
|
||||
controller->show(Box(ChannelsLimitBox, &peer->session()));
|
||||
});
|
||||
}
|
||||
primary->invokeForSessionController(
|
||||
&peer->session().account(),
|
||||
peer,
|
||||
[&](not_null<Window::SessionController*> controller) {
|
||||
controller->show(Box(ChannelsLimitBox, &peer->session()));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -2137,7 +2133,6 @@ void ApiWrap::saveDraftsToCloud() {
|
||||
if (const auto cloudDraft = history->cloudDraft(topicRootId)) {
|
||||
if (cloudDraft->saveRequestId == requestId) {
|
||||
history->clearCloudDraft(topicRootId);
|
||||
history->applyCloudDraft(topicRootId);
|
||||
}
|
||||
}
|
||||
const auto i = _draftsSaveRequestIds.find(weak);
|
||||
|
||||
@@ -20,7 +20,7 @@ void showBox(
|
||||
LayerOptions options,
|
||||
anim::type animated) {
|
||||
const auto window = Core::IsAppLaunched()
|
||||
? Core::App().primaryWindow()
|
||||
? Core::App().activePrimaryWindow()
|
||||
: nullptr;
|
||||
if (window) {
|
||||
window->show(std::move(content), options, animated);
|
||||
@@ -31,7 +31,7 @@ void showBox(
|
||||
|
||||
void hideLayer(anim::type animated) {
|
||||
const auto window = Core::IsAppLaunched()
|
||||
? Core::App().primaryWindow()
|
||||
? Core::App().activePrimaryWindow()
|
||||
: nullptr;
|
||||
if (window) {
|
||||
window->hideLayer(animated);
|
||||
@@ -40,7 +40,7 @@ void hideLayer(anim::type animated) {
|
||||
|
||||
bool isLayerShown() {
|
||||
const auto window = Core::IsAppLaunched()
|
||||
? Core::App().primaryWindow()
|
||||
? Core::App().activePrimaryWindow()
|
||||
: nullptr;
|
||||
return window && window->isLayerShown();
|
||||
}
|
||||
|
||||
@@ -69,10 +69,11 @@ void ChangeFilterById(
|
||||
MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter),
|
||||
MTP_int(filter.id()),
|
||||
filter.tl()
|
||||
)).done([=, chat = history->peer->name(), name = filter.title()]{
|
||||
)).done([=, chat = history->peer->name(), name = filter.title()] {
|
||||
// Since only the primary window has dialogs list,
|
||||
// We can safely show toast there.
|
||||
if (const auto controller = Core::App().primaryWindow()) {
|
||||
const auto account = &history->session().account();
|
||||
if (const auto controller = Core::App().windowFor(account)) {
|
||||
auto text = (add
|
||||
? tr::lng_filters_toast_add
|
||||
: tr::lng_filters_toast_remove)(
|
||||
|
||||
@@ -459,7 +459,7 @@ void ProxyRow::paintEvent(QPaintEvent *e) {
|
||||
void ProxyRow::paintCheck(Painter &p) {
|
||||
const auto loading = _progress
|
||||
? _progress->computeState()
|
||||
: Ui::RadialState{ 0., 0, FullArcLength };
|
||||
: Ui::RadialState{ 0., 0, arc::kFullLength };
|
||||
const auto toggled = _toggled.value(_view.selected ? 1. : 0.)
|
||||
* (1. - loading.shown);
|
||||
const auto _st = &st::defaultRadio;
|
||||
@@ -484,7 +484,7 @@ void ProxyRow::paintCheck(Painter &p) {
|
||||
_st->thickness,
|
||||
pen.color(),
|
||||
_st->bg);
|
||||
} else if (loading.arcLength < FullArcLength) {
|
||||
} else if (loading.arcLength < arc::kFullLength) {
|
||||
p.drawArc(rect, loading.arcFrom, loading.arcLength);
|
||||
} else {
|
||||
p.drawEllipse(rect);
|
||||
|
||||
@@ -147,7 +147,9 @@ void DownloadPathBox::setPathText(const QString &text) {
|
||||
DownloadPathBox::Directory DownloadPathBox::typeFromPath(
|
||||
const QString &path) {
|
||||
if (path.isEmpty()) {
|
||||
return Directory::Downloads;
|
||||
return Core::App().canReadDefaultDownloadPath(true)
|
||||
? Directory::Downloads
|
||||
: Directory::Temp;
|
||||
} else if (path == FileDialog::Tmp()) {
|
||||
return Directory::Temp;
|
||||
}
|
||||
|
||||
@@ -78,14 +78,11 @@ auto ListFromMimeData(not_null<const QMimeData*> data, bool premium) {
|
||||
: Ui::PreparedList(Error::EmptyFile, QString());
|
||||
if (result.error == Error::None) {
|
||||
return result;
|
||||
} else if (data->hasImage()) {
|
||||
auto image = qvariant_cast<QImage>(data->imageData());
|
||||
if (!image.isNull()) {
|
||||
return Storage::PrepareMediaFromImage(
|
||||
std::move(image),
|
||||
QByteArray(),
|
||||
st::sendMediaPreviewSize);
|
||||
}
|
||||
} else if (auto read = Core::ReadMimeImage(data)) {
|
||||
return Storage::PrepareMediaFromImage(
|
||||
std::move(read.image),
|
||||
std::move(read.content),
|
||||
st::sendMediaPreviewSize);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -451,7 +448,8 @@ void EditCaptionBox::setupPhotoEditorEventHandler() {
|
||||
&file.information->media);
|
||||
|
||||
image->modifications = mods;
|
||||
Storage::UpdateImageDetails(file, previewWidth);
|
||||
const auto sideLimit = PhotoSideLimit();
|
||||
Storage::UpdateImageDetails(file, previewWidth, sideLimit);
|
||||
rebuildPreview();
|
||||
};
|
||||
const auto fileImage = std::make_shared<Image>(*large);
|
||||
|
||||
@@ -590,11 +590,10 @@ void ChooseTopicSearchController::searchOnServer() {
|
||||
}
|
||||
delegate()->peerListSearchAddRow(topic->rootId().bare);
|
||||
});
|
||||
if (_offsetTopicId != savedTopicId) {
|
||||
delegate()->peerListSearchRefreshRows();
|
||||
} else {
|
||||
if (_offsetTopicId == savedTopicId) {
|
||||
_allLoaded = true;
|
||||
}
|
||||
delegate()->peerListSearchRefreshRows();
|
||||
}).fail([=] {
|
||||
_allLoaded = true;
|
||||
}).send();
|
||||
@@ -633,10 +632,13 @@ auto ChooseTopicBoxController::Row::generatePaintUserpicCallback(
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size) {
|
||||
const auto &st = st::forumTopicRow;
|
||||
x -= st.padding.left();
|
||||
y -= st.padding.top();
|
||||
auto view = Ui::PeerUserpicView();
|
||||
p.translate(x, y);
|
||||
_topic->paintUserpic(p, view, {
|
||||
.st = &st::forumTopicRow,
|
||||
.st = &st,
|
||||
.currentBg = st::windowBg,
|
||||
.now = crl::now(),
|
||||
.width = outerWidth,
|
||||
@@ -696,7 +698,7 @@ void ChooseTopicBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
|
||||
void ChooseTopicBoxController::prepare() {
|
||||
delegate()->peerListSetTitle(tr::lng_forward_choose());
|
||||
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
|
||||
setSearchNoResultsText(tr::lng_topics_not_found(tr::now));
|
||||
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
|
||||
refreshRows(true);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -25,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -208,88 +210,24 @@ void Controller::choose(not_null<ChatData*> chat) {
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> SetupAbout(
|
||||
not_null<QWidget*> parent,
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> About(
|
||||
not_null<ChannelData*> channel,
|
||||
ChannelData *chat) {
|
||||
auto about = object_ptr<Ui::FlatLabel>(
|
||||
parent,
|
||||
QString(),
|
||||
st::linkedChatAbout);
|
||||
about->setMarkedText([&] {
|
||||
if (!channel->isBroadcast()) {
|
||||
return tr::lng_manage_linked_channel_about(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(chat->name()),
|
||||
Ui::Text::WithEntities);
|
||||
} else if (chat != nullptr) {
|
||||
return tr::lng_manage_discussion_group_about_chosen(
|
||||
tr::now,
|
||||
lt_group,
|
||||
Ui::Text::Bold(chat->name()),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
return tr::lng_manage_discussion_group_about(
|
||||
tr::now,
|
||||
if (!channel->isBroadcast()) {
|
||||
return tr::lng_manage_linked_channel_about(
|
||||
lt_channel,
|
||||
rpl::single(Ui::Text::Bold(chat->name())),
|
||||
Ui::Text::WithEntities);
|
||||
}());
|
||||
return about;
|
||||
} else if (chat != nullptr) {
|
||||
return tr::lng_manage_discussion_group_about_chosen(
|
||||
lt_group,
|
||||
rpl::single(Ui::Text::Bold(chat->name())),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
return tr::lng_manage_discussion_group_about(Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> SetupFooter(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<ChannelData*> channel) {
|
||||
return object_ptr<Ui::FlatLabel>(
|
||||
parent,
|
||||
(channel->isBroadcast()
|
||||
? tr::lng_manage_discussion_group_posted
|
||||
: tr::lng_manage_linked_channel_posted)(),
|
||||
st::linkedChatAbout);
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> SetupCreateGroup(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChannelData*> channel,
|
||||
Fn<void(ChannelData*)> callback) {
|
||||
Expects(channel->isBroadcast());
|
||||
|
||||
auto result = object_ptr<Ui::SettingsButton>(
|
||||
parent,
|
||||
tr::lng_manage_discussion_group_create(
|
||||
) | Ui::Text::ToUpper(),
|
||||
st::infoCreateLinkedChatButton);
|
||||
result->addClickHandler([=] {
|
||||
const auto guarded = crl::guard(parent, callback);
|
||||
Window::Show(navigation).showBox(
|
||||
Box<GroupInfoBox>(
|
||||
navigation,
|
||||
GroupInfoBox::Type::Megagroup,
|
||||
channel->name() + " Chat",
|
||||
guarded),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> SetupUnlink(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<ChannelData*> channel,
|
||||
Fn<void(ChannelData*)> callback) {
|
||||
auto result = object_ptr<Ui::SettingsButton>(
|
||||
parent,
|
||||
(channel->isBroadcast()
|
||||
? tr::lng_manage_discussion_group_unlink
|
||||
: tr::lng_manage_linked_channel_unlink)() | Ui::Text::ToUpper(),
|
||||
st::infoUnlinkChatButton);
|
||||
result->addClickHandler([=] {
|
||||
callback(nullptr);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChannelData*> channel,
|
||||
ChannelData *chat,
|
||||
@@ -298,27 +236,77 @@ object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
Fn<void(ChannelData*)> callback) {
|
||||
Expects((channel->isBroadcast() && canEdit) || (chat != nullptr));
|
||||
|
||||
const auto init = [=](not_null<PeerListBox*> box) {
|
||||
class ListBox final : public PeerListBox {
|
||||
public:
|
||||
ListBox(
|
||||
QWidget *parent,
|
||||
std::unique_ptr<PeerListController> controller,
|
||||
Fn<void(not_null<ListBox*>)> init)
|
||||
: PeerListBox(
|
||||
parent,
|
||||
std::move(controller),
|
||||
[=](not_null<PeerListBox*>) { init(this); }) {
|
||||
}
|
||||
|
||||
void showFinished() override {
|
||||
_showFinished.fire({});
|
||||
}
|
||||
|
||||
rpl::producer<> showFinishes() const {
|
||||
return _showFinished.events();
|
||||
}
|
||||
|
||||
private:
|
||||
rpl::event_stream<> _showFinished;
|
||||
|
||||
};
|
||||
|
||||
const auto init = [=](not_null<ListBox*> box) {
|
||||
auto above = object_ptr<Ui::VerticalLayout>(box);
|
||||
above->add(
|
||||
SetupAbout(above, channel, chat),
|
||||
st::linkedChatAboutPadding);
|
||||
Settings::AddDividerTextWithLottie(
|
||||
above,
|
||||
box->showFinishes(),
|
||||
About(channel, chat),
|
||||
u"discussion"_q);
|
||||
if (!chat) {
|
||||
above->add(SetupCreateGroup(
|
||||
Assert(channel->isBroadcast());
|
||||
|
||||
Settings::AddSkip(above);
|
||||
Settings::AddButton(
|
||||
above,
|
||||
navigation,
|
||||
channel,
|
||||
callback));
|
||||
tr::lng_manage_discussion_group_create(),
|
||||
st::infoCreateLinkedChatButton,
|
||||
{ &st::settingsIconChat, Settings::kIconLightBlue }
|
||||
)->addClickHandler([=, parent = above.data()] {
|
||||
const auto guarded = crl::guard(parent, callback);
|
||||
Window::Show(navigation).showBox(
|
||||
Box<GroupInfoBox>(
|
||||
navigation,
|
||||
GroupInfoBox::Type::Megagroup,
|
||||
channel->name() + " Chat",
|
||||
guarded),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
}
|
||||
box->peerListSetAboveWidget(std::move(above));
|
||||
|
||||
auto below = object_ptr<Ui::VerticalLayout>(box);
|
||||
if (chat && canEdit) {
|
||||
below->add(SetupUnlink(below, channel, callback));
|
||||
Settings::AddButton(
|
||||
below,
|
||||
(channel->isBroadcast()
|
||||
? tr::lng_manage_discussion_group_unlink
|
||||
: tr::lng_manage_linked_channel_unlink)(),
|
||||
st::infoUnlinkChatButton,
|
||||
{ &st::settingsIconMinus, Settings::kIconRed }
|
||||
)->addClickHandler([=] { callback(nullptr); });
|
||||
Settings::AddSkip(below);
|
||||
}
|
||||
below->add(
|
||||
SetupFooter(below, channel),
|
||||
st::linkedChatAboutPadding);
|
||||
Settings::AddDividerText(
|
||||
below,
|
||||
(channel->isBroadcast()
|
||||
? tr::lng_manage_discussion_group_posted
|
||||
: tr::lng_manage_linked_channel_posted)());
|
||||
box->peerListSetBelowWidget(std::move(below));
|
||||
|
||||
box->setTitle(channel->isBroadcast()
|
||||
@@ -339,7 +327,7 @@ object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
std::move(chats),
|
||||
std::move(callback),
|
||||
std::move(showHistoryCallback));
|
||||
return Box<PeerListBox>(std::move(controller), init);
|
||||
return Box<ListBox>(std::move(controller), init);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -17,14 +17,14 @@ namespace Window {
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<ChannelData*> chat,
|
||||
bool canEdit,
|
||||
Fn<void(ChannelData*)> callback);
|
||||
|
||||
object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChannelData*> channel,
|
||||
std::vector<not_null<PeerData*>> &&chats,
|
||||
|
||||
@@ -1913,7 +1913,7 @@ void Controller::deleteChannel() {
|
||||
const auto session = &_peer->session();
|
||||
|
||||
_navigation->parentController()->hideLayer();
|
||||
Core::App().closeChatFromWindows(_peer);
|
||||
Core::App().closeChatFromWindows(channel);
|
||||
if (chat) {
|
||||
session->api().deleteConversation(chat, false);
|
||||
}
|
||||
|
||||
@@ -35,8 +35,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kFullArcLength = 360 * 16;
|
||||
|
||||
enum class Color {
|
||||
Permanent,
|
||||
Expiring,
|
||||
@@ -740,7 +738,7 @@ void LinksController::rowPaintIcon(
|
||||
margins,
|
||||
margins,
|
||||
margins,
|
||||
}), (kFullArcLength / 4), kFullArcLength * (1. - progress));
|
||||
}), arc::kQuarterLength, arc::kFullLength * (1. - progress));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -348,8 +348,9 @@ void SendFilesBox::enqueueNextPrepare() {
|
||||
_list.filesToProcess.pop_front();
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
_preparing = true;
|
||||
crl::async([weak, file = std::move(file)]() mutable {
|
||||
Storage::PrepareDetails(file, st::sendMediaPreviewSize);
|
||||
const auto sideLimit = PhotoSideLimit(); // Get on main thread.
|
||||
crl::async([weak, sideLimit, file = std::move(file)]() mutable {
|
||||
Storage::PrepareDetails(file, st::sendMediaPreviewSize, sideLimit);
|
||||
crl::on_main([weak, file = std::move(file)]() mutable {
|
||||
if (weak) {
|
||||
weak->addPreparedAsyncFile(std::move(file));
|
||||
@@ -975,14 +976,11 @@ bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
|
||||
QString());
|
||||
if (result.error == Ui::PreparedList::Error::None) {
|
||||
return result;
|
||||
} else if (data->hasImage()) {
|
||||
auto image = qvariant_cast<QImage>(data->imageData());
|
||||
if (!image.isNull()) {
|
||||
return Storage::PrepareMediaFromImage(
|
||||
std::move(image),
|
||||
QByteArray(),
|
||||
st::sendMediaPreviewSize);
|
||||
}
|
||||
} else if (auto read = Core::ReadMimeImage(data)) {
|
||||
return Storage::PrepareMediaFromImage(
|
||||
std::move(read.image),
|
||||
std::move(read.content),
|
||||
st::sendMediaPreviewSize);
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
|
||||
@@ -369,6 +369,10 @@ callCameraReDial: IconButton(callReDial) {
|
||||
icon: icon {{ "calls/call_camera_active", menuIconFg }};
|
||||
iconOver: icon {{ "calls/call_camera_active", menuIconFgOver }};
|
||||
}
|
||||
callGroupCall: IconButton(callCameraReDial) {
|
||||
icon: icon {{ "top_bar_group_call", menuIconFg }};
|
||||
iconOver: icon {{ "top_bar_group_call", menuIconFgOver }};
|
||||
}
|
||||
|
||||
callRatingPadding: margins(24px, 12px, 24px, 0px);
|
||||
callRatingStar: IconButton {
|
||||
|
||||
@@ -25,6 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_peer_values.h" // Data::ChannelHasActiveCall.
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "boxes/delete_messages_box.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "api/api_updates.h"
|
||||
@@ -40,8 +43,200 @@ namespace {
|
||||
constexpr auto kFirstPageCount = 20;
|
||||
constexpr auto kPerPageCount = 100;
|
||||
|
||||
class GroupCallRow final : public PeerListRow {
|
||||
public:
|
||||
GroupCallRow(not_null<PeerData*> peer);
|
||||
|
||||
void rightActionAddRipple(
|
||||
QPoint point,
|
||||
Fn<void()> updateCallback) override;
|
||||
void rightActionStopLastRipple() override;
|
||||
|
||||
int paintNameIconGetWidth(
|
||||
Painter &p,
|
||||
Fn<void()> repaint,
|
||||
crl::time now,
|
||||
int nameLeft,
|
||||
int nameTop,
|
||||
int nameWidth,
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected) override {
|
||||
return 0;
|
||||
}
|
||||
QSize rightActionSize() const override {
|
||||
return peer()->isChannel() ? QSize(_st.width, _st.height) : QSize();
|
||||
}
|
||||
QMargins rightActionMargins() const override {
|
||||
return QMargins(
|
||||
0,
|
||||
0,
|
||||
st::defaultPeerListItem.photoPosition.x(),
|
||||
0);
|
||||
}
|
||||
void rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) override;
|
||||
|
||||
private:
|
||||
const style::IconButton &_st;
|
||||
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
|
||||
|
||||
};
|
||||
|
||||
GroupCallRow::GroupCallRow(not_null<PeerData*> peer)
|
||||
: PeerListRow(peer)
|
||||
, _st(st::callGroupCall) {
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
const auto status = (channel->isMegagroup()
|
||||
? (channel->isPublic()
|
||||
? tr::lng_create_public_channel_title
|
||||
: tr::lng_create_private_channel_title)
|
||||
: (channel->isPublic()
|
||||
? tr::lng_create_public_group_title
|
||||
: tr::lng_create_private_group_title))(tr::now);
|
||||
setCustomStatus(status.toLower());
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCallRow::rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
auto size = rightActionSize();
|
||||
if (_actionRipple) {
|
||||
_actionRipple->paint(
|
||||
p,
|
||||
x + _st.rippleAreaPosition.x(),
|
||||
y + _st.rippleAreaPosition.y(),
|
||||
outerWidth);
|
||||
if (_actionRipple->empty()) {
|
||||
_actionRipple.reset();
|
||||
}
|
||||
}
|
||||
_st.icon.paintInCenter(
|
||||
p,
|
||||
style::rtlrect(x, y, size.width(), size.height(), outerWidth));
|
||||
}
|
||||
|
||||
void GroupCallRow::rightActionAddRipple(
|
||||
QPoint point,
|
||||
Fn<void()> updateCallback) {
|
||||
if (!_actionRipple) {
|
||||
auto mask = Ui::RippleAnimation::EllipseMask(
|
||||
QSize(_st.rippleAreaSize, _st.rippleAreaSize));
|
||||
_actionRipple = std::make_unique<Ui::RippleAnimation>(
|
||||
_st.ripple,
|
||||
std::move(mask),
|
||||
std::move(updateCallback));
|
||||
}
|
||||
_actionRipple->add(point - _st.rippleAreaPosition);
|
||||
}
|
||||
|
||||
void GroupCallRow::rightActionStopLastRipple() {
|
||||
if (_actionRipple) {
|
||||
_actionRipple->lastStop();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace GroupCalls {
|
||||
|
||||
ListController::ListController(not_null<Window::SessionController*> window)
|
||||
: _window(window) {
|
||||
setStyleOverrides(&st::peerListSingleRow);
|
||||
}
|
||||
|
||||
Main::Session &ListController::session() const {
|
||||
return _window->session();
|
||||
}
|
||||
|
||||
void ListController::prepare() {
|
||||
const auto removeRow = [=](not_null<PeerData*> peer) {
|
||||
const auto it = _groupCalls.find(peer->id);
|
||||
if (it != end(_groupCalls)) {
|
||||
const auto &row = it->second;
|
||||
delegate()->peerListRemoveRow(row);
|
||||
_groupCalls.erase(it);
|
||||
}
|
||||
};
|
||||
const auto createRow = [=](not_null<PeerData*> peer) {
|
||||
const auto it = _groupCalls.find(peer->id);
|
||||
if (it == end(_groupCalls)) {
|
||||
auto row = std::make_unique<GroupCallRow>(peer);
|
||||
_groupCalls.emplace(peer->id, row.get());
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
};
|
||||
|
||||
const auto processPeer = [=](PeerData *peer) {
|
||||
if (!peer) {
|
||||
return;
|
||||
}
|
||||
const auto channel = peer->asChannel();
|
||||
if (channel && Data::ChannelHasActiveCall(channel)) {
|
||||
createRow(peer);
|
||||
} else {
|
||||
removeRow(peer);
|
||||
}
|
||||
};
|
||||
const auto finishProcess = [=] {
|
||||
delegate()->peerListRefreshRows();
|
||||
_fullCount = delegate()->peerListFullRowsCount();
|
||||
};
|
||||
|
||||
session().changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||
processPeer(update.peer);
|
||||
finishProcess();
|
||||
}, lifetime());
|
||||
|
||||
{
|
||||
auto count = 0;
|
||||
const auto list = session().data().chatsList(nullptr);
|
||||
for (const auto &key : list->pinned()->order()) {
|
||||
processPeer(key.peer());
|
||||
}
|
||||
for (const auto &key : list->indexed()->all()) {
|
||||
if (count > kFirstPageCount) {
|
||||
break;
|
||||
}
|
||||
processPeer(key->key().peer());
|
||||
count++;
|
||||
}
|
||||
finishProcess();
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<bool> ListController::shownValue() const {
|
||||
return _fullCount.value(
|
||||
) | rpl::map(rpl::mappers::_1 > 0) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
void ListController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto window = _window;
|
||||
crl::on_main(window, [=, peer = row->peer()] {
|
||||
window->showPeerHistory(
|
||||
peer,
|
||||
Window::SectionShow::Way::ClearStack);
|
||||
});
|
||||
}
|
||||
|
||||
void ListController::rowRightActionClicked(not_null<PeerListRow*> row) {
|
||||
_window->startOrJoinGroupCall(row->peer());
|
||||
}
|
||||
|
||||
} // namespace GroupCalls
|
||||
|
||||
class BoxController::Row : public PeerListRow {
|
||||
public:
|
||||
Row(not_null<HistoryItem*> item);
|
||||
|
||||
@@ -15,6 +15,27 @@ class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Calls {
|
||||
namespace GroupCalls {
|
||||
|
||||
class ListController : public PeerListController {
|
||||
public:
|
||||
explicit ListController(not_null<Window::SessionController*> window);
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> shownValue() const;
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
private:
|
||||
const not_null<Window::SessionController*> _window;
|
||||
base::flat_map<PeerId, not_null<PeerListRow*>> _groupCalls;
|
||||
rpl::variable<int> _fullCount;
|
||||
|
||||
};
|
||||
|
||||
} // namespace GroupCalls
|
||||
|
||||
class BoxController : public PeerListController {
|
||||
public:
|
||||
@@ -34,6 +55,7 @@ private:
|
||||
void receivedCalls(const QVector<MTPMessage> &result);
|
||||
void refreshAbout();
|
||||
|
||||
class GroupCallRow;
|
||||
class Row;
|
||||
Row *rowForItem(not_null<const HistoryItem*> item);
|
||||
|
||||
|
||||
@@ -275,6 +275,14 @@ TopBar::TopBar(
|
||||
, _updateDurationTimer([=] { updateDurationText(); }) {
|
||||
initControls();
|
||||
resize(width(), st::callBarHeight);
|
||||
setupInitialBrush();
|
||||
}
|
||||
|
||||
void TopBar::setupInitialBrush() {
|
||||
Expects(_switchStateCallback != nullptr);
|
||||
|
||||
_switchStateAnimation.stop();
|
||||
_switchStateCallback(1.);
|
||||
}
|
||||
|
||||
void TopBar::initControls() {
|
||||
@@ -316,14 +324,16 @@ void TopBar::initControls() {
|
||||
| MapPushToTalkToActive()
|
||||
| rpl::distinct_until_changed()
|
||||
| rpl::type_erased()),
|
||||
_groupCall->instanceStateValue(),
|
||||
rpl::single(
|
||||
_groupCall->instanceState()
|
||||
) | rpl::then(_groupCall->instanceStateValue() | rpl::filter(
|
||||
_1 != GroupCall::InstanceState::TransitionToRtc)),
|
||||
rpl::single(
|
||||
_groupCall->scheduleDate()
|
||||
) | rpl::then(_groupCall->real(
|
||||
) | rpl::map([](not_null<Data::GroupCall*> call) {
|
||||
return call->scheduleDateValue();
|
||||
}) | rpl::flatten_latest())
|
||||
) | rpl::filter(_2 != GroupCall::InstanceState::TransitionToRtc);
|
||||
}) | rpl::flatten_latest()));
|
||||
std::move(
|
||||
muted
|
||||
) | rpl::map(
|
||||
@@ -350,7 +360,7 @@ void TopBar::initControls() {
|
||||
const auto crossFrom = (fromMuted != BarState::Active) ? 1. : 0.;
|
||||
const auto crossTo = (toMuted != BarState::Active) ? 1. : 0.;
|
||||
|
||||
auto animationCallback = [=](float64 value) {
|
||||
_switchStateCallback = [=](float64 value) {
|
||||
if (_groupCall) {
|
||||
_groupBrush = QBrush(
|
||||
_gradients.gradient(fromMuted, toMuted, value));
|
||||
@@ -366,7 +376,7 @@ void TopBar::initControls() {
|
||||
_switchStateAnimation.stop();
|
||||
const auto duration = (to - from) * kSwitchStateDuration;
|
||||
_switchStateAnimation.start(
|
||||
std::move(animationCallback),
|
||||
_switchStateCallback,
|
||||
from,
|
||||
to,
|
||||
duration);
|
||||
@@ -748,6 +758,9 @@ void TopBar::updateControlsGeometry() {
|
||||
_gradients.set_points(
|
||||
QPointF(0, st::callBarHeight / 2),
|
||||
QPointF(width(), st::callBarHeight / 2));
|
||||
if (!_switchStateAnimation.animating()) {
|
||||
_switchStateCallback(1.);
|
||||
}
|
||||
}
|
||||
|
||||
void TopBar::paintEvent(QPaintEvent *e) {
|
||||
|
||||
@@ -68,6 +68,7 @@ private:
|
||||
const base::weak_ptr<GroupCall> &groupCall);
|
||||
|
||||
void initControls();
|
||||
void setupInitialBrush();
|
||||
void updateInfoLabels();
|
||||
void setInfoLabels();
|
||||
void updateDurationText();
|
||||
@@ -101,6 +102,7 @@ private:
|
||||
QBrush _groupBrush;
|
||||
anim::linear_gradients<BarState> _gradients;
|
||||
Ui::Animations::Simple _switchStateAnimation;
|
||||
Fn<void(float64)> _switchStateCallback;
|
||||
|
||||
base::Timer _updateDurationTimer;
|
||||
|
||||
|
||||
@@ -1194,10 +1194,7 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
const auto admin = IsGroupCallAdmin(_peer, participantPeer);
|
||||
const auto session = &_peer->session();
|
||||
const auto getCurrentWindow = [=]() -> Window::SessionController* {
|
||||
if (const auto window = Core::App().separateWindowForPeer(
|
||||
participantPeer)) {
|
||||
return window->sessionController();
|
||||
} else if (const auto window = Core::App().primaryWindow()) {
|
||||
if (const auto window = Core::App().windowFor(participantPeer)) {
|
||||
if (const auto controller = window->sessionController()) {
|
||||
if (&controller->session() == session) {
|
||||
return controller;
|
||||
|
||||
@@ -1064,7 +1064,6 @@ void EmojiListWidget::drawCustom(
|
||||
QPoint position,
|
||||
int set,
|
||||
int index) {
|
||||
position += _innerPosition + _customPosition;
|
||||
auto &custom = _custom[set];
|
||||
custom.painted = true;
|
||||
auto &entry = custom.list[index];
|
||||
|
||||
@@ -262,7 +262,7 @@ void Row::paintRadio(QPainter &p) {
|
||||
}
|
||||
const auto loading = _loading
|
||||
? _loading->computeState()
|
||||
: Ui::RadialState{ 0., 0, FullArcLength };
|
||||
: Ui::RadialState{ 0., 0, arc::kFullLength };
|
||||
const auto isToggledSet = v::is<Active>(_state.current());
|
||||
const auto isActiveSet = isToggledSet || v::is<Loading>(_state.current());
|
||||
const auto toggled = _toggled.value(isToggledSet ? 1. : 0.);
|
||||
@@ -301,7 +301,7 @@ void Row::paintRadio(QPainter &p) {
|
||||
_st->thickness,
|
||||
pen.color(),
|
||||
_st->bg);
|
||||
} else if (loading.arcLength < FullArcLength) {
|
||||
} else if (loading.arcLength < arc::kFullLength) {
|
||||
p.drawArc(rect, loading.arcFrom, loading.arcLength);
|
||||
} else {
|
||||
p.drawEllipse(rect);
|
||||
|
||||
@@ -68,7 +68,7 @@ QImage EmojiImageLoader::prepare(EmojiPtr emoji) const {
|
||||
{ -1, 1 },
|
||||
{ 1, 1 },
|
||||
} };
|
||||
const auto corrected = int(base::SafeRound(delta / sqrt(2.)));
|
||||
const auto corrected = int(base::SafeRound(delta / M_SQRT2));
|
||||
for (const auto &shift : diagonal) {
|
||||
for (auto i = 0; i != corrected; ++i) {
|
||||
p.drawImage(QPoint(delta, delta) + shift * (i + 1), tinted);
|
||||
|
||||
@@ -65,6 +65,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "media/player/media_player_float.h"
|
||||
#include "media/clip/media_clip_reader.h" // For Media::Clip::Finish().
|
||||
#include "media/system_media_controls_manager.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/window_lock_widgets.h"
|
||||
@@ -90,6 +91,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/connection_box.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "styles/style_window.h"
|
||||
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QMimeDatabase>
|
||||
@@ -183,11 +185,11 @@ Application::~Application() {
|
||||
Local::writeSettings();
|
||||
}
|
||||
|
||||
// Depend on primaryWindow() for now :(
|
||||
Shortcuts::Finish();
|
||||
|
||||
setLastActiveWindow(nullptr);
|
||||
_windowInSettings = _lastActivePrimaryWindow = nullptr;
|
||||
_closingAsyncWindows.clear();
|
||||
_secondaryWindows.clear();
|
||||
_primaryWindow = nullptr;
|
||||
_primaryWindows.clear();
|
||||
_mediaView = nullptr;
|
||||
_notifications->clearAllFast();
|
||||
|
||||
@@ -214,6 +216,8 @@ Application::~Application() {
|
||||
|
||||
Window::Theme::Uninitialize();
|
||||
|
||||
_mediaControlsManager = nullptr;
|
||||
|
||||
Media::Player::finish(_audio.get());
|
||||
style::stopManager();
|
||||
|
||||
@@ -237,8 +241,7 @@ void Application::run() {
|
||||
refreshGlobalProxy(); // Depends on app settings being read.
|
||||
|
||||
if (const auto old = Local::oldSettingsVersion(); old < AppVersion) {
|
||||
Platform::InstallLauncher();
|
||||
RegisterUrlScheme();
|
||||
InvokeQueued(this, [] { RegisterUrlScheme(); });
|
||||
Platform::NewVersionLaunched(old);
|
||||
}
|
||||
|
||||
@@ -252,15 +255,6 @@ void Application::run() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (KSandbox::isInside()) {
|
||||
const auto path = settings().downloadPath();
|
||||
if (!path.isEmpty()
|
||||
&& path != FileDialog::Tmp()
|
||||
&& !base::CanReadDirectory(path)) {
|
||||
settings().setDownloadPath(QString());
|
||||
}
|
||||
}
|
||||
|
||||
_translator = std::make_unique<Lang::Translator>();
|
||||
QCoreApplication::instance()->installTranslator(_translator.get());
|
||||
|
||||
@@ -269,10 +263,15 @@ void Application::run() {
|
||||
Ui::StartCachedCorners();
|
||||
Ui::Emoji::Init();
|
||||
Ui::PreloadTextSpoilerMask();
|
||||
startShortcuts();
|
||||
startEmojiImageLoader();
|
||||
startSystemDarkModeViewer();
|
||||
Media::Player::start(_audio.get());
|
||||
|
||||
if (MediaControlsManager::Supported()) {
|
||||
_mediaControlsManager = std::make_unique<MediaControlsManager>();
|
||||
}
|
||||
|
||||
style::ShortAnimationPlaying(
|
||||
) | rpl::start_with_next([=](bool playing) {
|
||||
if (playing) {
|
||||
@@ -289,13 +288,14 @@ void Application::run() {
|
||||
// Create mime database, so it won't be slow later.
|
||||
QMimeDatabase().mimeTypeForName(u"text/plain"_q);
|
||||
|
||||
_primaryWindow = std::make_unique<Window::Controller>();
|
||||
_lastActiveWindow = _primaryWindow.get();
|
||||
_primaryWindows.emplace(nullptr, std::make_unique<Window::Controller>());
|
||||
setLastActiveWindow(_primaryWindows.front().second.get());
|
||||
_windowInSettings = _lastActivePrimaryWindow = _lastActiveWindow;
|
||||
|
||||
_domain->activeChanges(
|
||||
) | rpl::start_with_next([=](not_null<Main::Account*> account) {
|
||||
_primaryWindow->showAccount(account);
|
||||
}, _primaryWindow->widget()->lifetime());
|
||||
showAccount(account);
|
||||
}, _lifetime);
|
||||
|
||||
(
|
||||
_domain->activeValue(
|
||||
@@ -312,15 +312,15 @@ void Application::run() {
|
||||
) | rpl::start_with_next([=](not_null<Main::Account*> account) {
|
||||
const auto ordered = _domain->orderedAccounts();
|
||||
const auto it = ranges::find(ordered, account);
|
||||
if (it != end(ordered)) {
|
||||
if (_lastActivePrimaryWindow && it != end(ordered)) {
|
||||
const auto index = std::distance(begin(ordered), it);
|
||||
if ((index + 1) > _domain->maxAccounts()) {
|
||||
_primaryWindow->show(Box(
|
||||
_lastActivePrimaryWindow->show(Box(
|
||||
AccountsLimitBox,
|
||||
&account->session()));
|
||||
}
|
||||
}
|
||||
}, _primaryWindow->widget()->lifetime());
|
||||
}, _lifetime);
|
||||
|
||||
QCoreApplication::instance()->installEventFilter(this);
|
||||
|
||||
@@ -335,26 +335,23 @@ void Application::run() {
|
||||
|
||||
DEBUG_LOG(("Application Info: window created..."));
|
||||
|
||||
// Depend on primaryWindow() for now :(
|
||||
startShortcuts();
|
||||
startDomain();
|
||||
|
||||
startTray();
|
||||
|
||||
_primaryWindow->widget()->show();
|
||||
_lastActivePrimaryWindow->widget()->show();
|
||||
|
||||
const auto currentGeometry = _primaryWindow->widget()->geometry();
|
||||
const auto current = _lastActivePrimaryWindow->widget()->geometry();
|
||||
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||
_primaryWindow->widget()->Ui::RpWidget::setGeometry(currentGeometry);
|
||||
_lastActivePrimaryWindow->widget()->Ui::RpWidget::setGeometry(current);
|
||||
|
||||
DEBUG_LOG(("Application Info: showing."));
|
||||
_primaryWindow->finishFirstShow();
|
||||
_lastActivePrimaryWindow->finishFirstShow();
|
||||
|
||||
if (!_primaryWindow->locked() && cStartToSettings()) {
|
||||
_primaryWindow->showSettings();
|
||||
if (!_lastActivePrimaryWindow->locked() && cStartToSettings()) {
|
||||
_lastActivePrimaryWindow->showSettings();
|
||||
}
|
||||
|
||||
_primaryWindow->updateIsActiveFocus();
|
||||
_lastActivePrimaryWindow->updateIsActiveFocus();
|
||||
|
||||
for (const auto &error : Shortcuts::Errors()) {
|
||||
LOG(("Shortcuts Error: %1").arg(error));
|
||||
@@ -371,11 +368,6 @@ void Application::run() {
|
||||
_mediaView->show(std::move(request));
|
||||
}
|
||||
}, _lifetime);
|
||||
_primaryWindow->openInMediaViewRequests(
|
||||
) | rpl::start_to_stream(
|
||||
_openInMediaViewRequests,
|
||||
_primaryWindow->lifetime());
|
||||
|
||||
{
|
||||
const auto countries = std::make_shared<Countries::Manager>(
|
||||
_domain.get());
|
||||
@@ -383,6 +375,25 @@ void Application::run() {
|
||||
[[maybe_unused]] const auto countriesCopy = countries;
|
||||
});
|
||||
}
|
||||
|
||||
processCreatedWindow(_lastActivePrimaryWindow);
|
||||
}
|
||||
|
||||
void Application::showAccount(not_null<Main::Account*> account) {
|
||||
if (const auto separate = separateWindowForAccount(account)) {
|
||||
_lastActivePrimaryWindow = separate;
|
||||
separate->activate();
|
||||
} else if (const auto last = activePrimaryWindow()) {
|
||||
for (auto &[key, window] : _primaryWindows) {
|
||||
if (window.get() == last && key != account.get()) {
|
||||
auto found = std::move(window);
|
||||
_primaryWindows.remove(key);
|
||||
_primaryWindows.emplace(account, std::move(found));
|
||||
break;
|
||||
}
|
||||
}
|
||||
last->showAccount(account);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::showOpenGLCrashNotification() {
|
||||
@@ -399,7 +410,7 @@ void Application::showOpenGLCrashNotification() {
|
||||
Core::App().settings().setDisableOpenGL(true);
|
||||
Local::writeSettings();
|
||||
};
|
||||
_primaryWindow->show(Ui::MakeConfirmBox({
|
||||
_lastActivePrimaryWindow->show(Ui::MakeConfirmBox({
|
||||
.text = ""
|
||||
"There may be a problem with your graphics drivers and OpenGL. "
|
||||
"Try updating your drivers.\n\n"
|
||||
@@ -456,15 +467,15 @@ void Application::startSystemDarkModeViewer() {
|
||||
|
||||
void Application::enumerateWindows(Fn<void(
|
||||
not_null<Window::Controller*>)> callback) const {
|
||||
if (_primaryWindow) {
|
||||
callback(_primaryWindow.get());
|
||||
for (const auto &window : ranges::views::values(_primaryWindows)) {
|
||||
callback(window.get());
|
||||
}
|
||||
for (const auto &window : ranges::views::values(_secondaryWindows)) {
|
||||
callback(window.get());
|
||||
}
|
||||
}
|
||||
|
||||
void Application::processSecondaryWindow(
|
||||
void Application::processCreatedWindow(
|
||||
not_null<Window::Controller*> window) {
|
||||
window->openInMediaViewRequests(
|
||||
) | rpl::start_to_stream(_openInMediaViewRequests, window->lifetime());
|
||||
@@ -477,21 +488,35 @@ void Application::startTray() {
|
||||
) | rpl::start_with_next([=] {
|
||||
enumerateWindows([&](WindowRaw w) { w->updateIsActive(); });
|
||||
_tray->updateMenuText();
|
||||
}, _primaryWindow->widget()->lifetime());
|
||||
}, _lifetime);
|
||||
|
||||
_tray->showFromTrayRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto last = _lastActiveWindow;
|
||||
enumerateWindows([&](WindowRaw w) { w->widget()->showFromTray(); });
|
||||
if (last) {
|
||||
last->widget()->showFromTray();
|
||||
}
|
||||
}, _primaryWindow->widget()->lifetime());
|
||||
activate();
|
||||
}, _lifetime);
|
||||
|
||||
_tray->hideToTrayRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
enumerateWindows([&](WindowRaw w) { w->widget()->minimizeToTray(); });
|
||||
}, _primaryWindow->widget()->lifetime());
|
||||
enumerateWindows([&](WindowRaw w) {
|
||||
w->widget()->minimizeToTray();
|
||||
});
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Application::activate() {
|
||||
const auto last = _lastActiveWindow;
|
||||
const auto primary = _lastActivePrimaryWindow;
|
||||
enumerateWindows([&](not_null<Window::Controller*> w) {
|
||||
if (w != last && w != primary) {
|
||||
w->widget()->showFromTray();
|
||||
}
|
||||
});
|
||||
if (primary) {
|
||||
primary->widget()->showFromTray();
|
||||
}
|
||||
if (last && last != primary) {
|
||||
last->widget()->showFromTray();
|
||||
}
|
||||
}
|
||||
|
||||
auto Application::prepareEmojiSourceImages()
|
||||
@@ -513,10 +538,10 @@ void Application::clearEmojiSourceImages() {
|
||||
}
|
||||
|
||||
bool Application::isActiveForTrayMenu() const {
|
||||
if (_primaryWindow && _primaryWindow->widget()->isActiveForTrayMenu()) {
|
||||
return true;
|
||||
}
|
||||
return ranges::any_of(ranges::views::values(_secondaryWindows), [=](
|
||||
return ranges::any_of(ranges::views::values(_primaryWindows), [=](
|
||||
const std::unique_ptr<Window::Controller> &controller) {
|
||||
return controller->widget()->isActiveForTrayMenu();
|
||||
}) || ranges::any_of(ranges::views::values(_secondaryWindows), [=](
|
||||
const std::unique_ptr<Window::Controller> &controller) {
|
||||
return controller->widget()->isActiveForTrayMenu();
|
||||
});
|
||||
@@ -551,7 +576,7 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
|
||||
const auto event = static_cast<QShortcutEvent*>(e);
|
||||
DEBUG_LOG(("Shortcut event caught: %1"
|
||||
).arg(event->key().toString()));
|
||||
if (Shortcuts::HandleEvent(event)) {
|
||||
if (Shortcuts::HandleEvent(object, event)) {
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
@@ -571,8 +596,8 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
|
||||
cSetStartUrl(url.mid(0, 8192));
|
||||
checkStartUrl();
|
||||
}
|
||||
if (StartUrlRequiresActivate(url)) {
|
||||
_primaryWindow->activate();
|
||||
if (_lastActivePrimaryWindow && StartUrlRequiresActivate(url)) {
|
||||
_lastActivePrimaryWindow->activate();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
@@ -606,8 +631,7 @@ bool Application::canReadDefaultDownloadPath(bool always) const {
|
||||
}
|
||||
|
||||
bool Application::canSaveFileWithoutAskingForPath() const {
|
||||
return !Core::App().settings().askDownloadPath()
|
||||
&& canReadDefaultDownloadPath();
|
||||
return !Core::App().settings().askDownloadPath();
|
||||
}
|
||||
|
||||
MTP::Config &Application::fallbackProductionConfig() const {
|
||||
@@ -714,35 +738,12 @@ bool Application::screenIsLocked() const {
|
||||
return _screenIsLocked;
|
||||
}
|
||||
|
||||
void Application::setDefaultFloatPlayerDelegate(
|
||||
not_null<Media::Player::FloatDelegate*> delegate) {
|
||||
Expects(!_defaultFloatPlayerDelegate == !_floatPlayers);
|
||||
|
||||
_defaultFloatPlayerDelegate = delegate;
|
||||
_replacementFloatPlayerDelegate = nullptr;
|
||||
if (_floatPlayers) {
|
||||
_floatPlayers->replaceDelegate(delegate);
|
||||
} else {
|
||||
_floatPlayers = std::make_unique<Media::Player::FloatController>(
|
||||
delegate);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::replaceFloatPlayerDelegate(
|
||||
not_null<Media::Player::FloatDelegate*> replacement) {
|
||||
Expects(_floatPlayers != nullptr);
|
||||
|
||||
_replacementFloatPlayerDelegate = replacement;
|
||||
_floatPlayers->replaceDelegate(replacement);
|
||||
}
|
||||
|
||||
void Application::restoreFloatPlayerDelegate(
|
||||
not_null<Media::Player::FloatDelegate*> replacement) {
|
||||
Expects(_floatPlayers != nullptr);
|
||||
|
||||
if (_replacementFloatPlayerDelegate == replacement) {
|
||||
_replacementFloatPlayerDelegate = nullptr;
|
||||
_floatPlayers->replaceDelegate(_defaultFloatPlayerDelegate);
|
||||
void Application::floatPlayerToggleGifsPaused(bool paused) {
|
||||
_floatPlayerGifsPaused = paused;
|
||||
if (_lastActiveWindow) {
|
||||
if (const auto delegate = _lastActiveWindow->floatPlayerDelegate()) {
|
||||
delegate->floatPlayerToggleGifsPaused(paused);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,15 +815,15 @@ void Application::checkLocalTime() {
|
||||
|
||||
void Application::handleAppActivated() {
|
||||
checkLocalTime();
|
||||
if (_primaryWindow) {
|
||||
_primaryWindow->updateIsActiveFocus();
|
||||
if (_lastActiveWindow) {
|
||||
_lastActiveWindow->updateIsActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::handleAppDeactivated() {
|
||||
if (_primaryWindow) {
|
||||
_primaryWindow->updateIsActiveBlur();
|
||||
}
|
||||
enumerateWindows([&](not_null<Window::Controller*> w) {
|
||||
w->updateIsActiveBlur();
|
||||
});
|
||||
const auto session = _lastActiveWindow
|
||||
? _lastActiveWindow->maybeSession()
|
||||
: nullptr;
|
||||
@@ -855,8 +856,8 @@ void Application::switchDebugMode() {
|
||||
Logs::SetDebugEnabled(true);
|
||||
_launcher->writeDebugModeSetting();
|
||||
DEBUG_LOG(("Debug logs started."));
|
||||
if (_primaryWindow) {
|
||||
_primaryWindow->hideLayer();
|
||||
if (_lastActivePrimaryWindow) {
|
||||
_lastActivePrimaryWindow->hideLayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -969,13 +970,17 @@ bool Application::canApplyLangPackWithoutRestart() const {
|
||||
}
|
||||
|
||||
void Application::checkSendPaths() {
|
||||
if (!cSendPaths().isEmpty() && _primaryWindow && !_primaryWindow->locked()) {
|
||||
_primaryWindow->widget()->sendPaths();
|
||||
if (!cSendPaths().isEmpty()
|
||||
&& _lastActivePrimaryWindow
|
||||
&& !_lastActivePrimaryWindow->locked()) {
|
||||
_lastActivePrimaryWindow->widget()->sendPaths();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::checkStartUrl() {
|
||||
if (!cStartUrl().isEmpty() && _primaryWindow && !_primaryWindow->locked()) {
|
||||
if (!cStartUrl().isEmpty()
|
||||
&& _lastActivePrimaryWindow
|
||||
&& !_lastActivePrimaryWindow->locked()) {
|
||||
const auto url = cStartUrl();
|
||||
cSetStartUrl(QString());
|
||||
if (!openLocalUrl(url, {})) {
|
||||
@@ -1039,8 +1044,8 @@ bool Application::openCustomUrl(
|
||||
const auto my = context.value<ClickHandlerContext>();
|
||||
const auto controller = my.sessionWindow.get()
|
||||
? my.sessionWindow.get()
|
||||
: _primaryWindow
|
||||
? _primaryWindow->sessionController()
|
||||
: _lastActivePrimaryWindow
|
||||
? _lastActivePrimaryWindow->sessionController()
|
||||
: nullptr;
|
||||
|
||||
using namespace qthelp;
|
||||
@@ -1052,11 +1057,10 @@ bool Application::openCustomUrl(
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
void Application::preventOrInvoke(Fn<void()> &&callback) {
|
||||
_primaryWindow->preventOrInvoke(std::move(callback));
|
||||
_lastActivePrimaryWindow->preventOrInvoke(std::move(callback));
|
||||
}
|
||||
|
||||
void Application::lockByPasscode() {
|
||||
@@ -1159,8 +1163,13 @@ void Application::localPasscodeChanged() {
|
||||
checkAutoLock(crl::now());
|
||||
}
|
||||
|
||||
bool Application::savingPositionFor(
|
||||
not_null<Window::Controller*> window) const {
|
||||
return !_windowInSettings || (_windowInSettings == window);
|
||||
}
|
||||
|
||||
bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
|
||||
if (Quitting() || !_primaryWindow) {
|
||||
if (Quitting() || !_lastActiveWindow) {
|
||||
return false;
|
||||
} else if (_calls->hasActivePanel(session)) {
|
||||
return true;
|
||||
@@ -1171,8 +1180,18 @@ bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
Window::Controller *Application::primaryWindow() const {
|
||||
return _primaryWindow.get();
|
||||
Window::Controller *Application::activePrimaryWindow() const {
|
||||
return _lastActivePrimaryWindow;
|
||||
}
|
||||
|
||||
Window::Controller *Application::separateWindowForAccount(
|
||||
not_null<Main::Account*> account) const {
|
||||
for (const auto &[openedAccount, window] : _primaryWindows) {
|
||||
if (openedAccount == account.get()) {
|
||||
return window.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Window::Controller *Application::separateWindowForPeer(
|
||||
@@ -1204,35 +1223,152 @@ Window::Controller *Application::ensureSeparateWindowForPeer(
|
||||
peer->owner().history(peer),
|
||||
std::make_unique<Window::Controller>(peer, showAtMsgId)
|
||||
).first->second.get();
|
||||
processSecondaryWindow(result);
|
||||
processCreatedWindow(result);
|
||||
result->widget()->show();
|
||||
result->finishFirstShow();
|
||||
return activate(result);
|
||||
}
|
||||
|
||||
Window::Controller *Application::ensureSeparateWindowForAccount(
|
||||
not_null<Main::Account*> account) {
|
||||
const auto activate = [&](not_null<Window::Controller*> window) {
|
||||
window->activate();
|
||||
return window;
|
||||
};
|
||||
|
||||
if (const auto existing = separateWindowForAccount(account)) {
|
||||
return activate(existing);
|
||||
}
|
||||
const auto result = _primaryWindows.emplace(
|
||||
account,
|
||||
std::make_unique<Window::Controller>(account)
|
||||
).first->second.get();
|
||||
processCreatedWindow(result);
|
||||
result->widget()->show();
|
||||
result->finishFirstShow();
|
||||
return activate(result);
|
||||
}
|
||||
|
||||
Window::Controller *Application::windowFor(not_null<PeerData*> peer) const {
|
||||
if (const auto separate = separateWindowForPeer(peer)) {
|
||||
return separate;
|
||||
}
|
||||
return windowFor(&peer->account());
|
||||
}
|
||||
|
||||
Window::Controller *Application::windowFor(
|
||||
not_null<Main::Account*> account) const {
|
||||
if (const auto separate = separateWindowForAccount(account)) {
|
||||
return separate;
|
||||
}
|
||||
return activePrimaryWindow();
|
||||
}
|
||||
|
||||
Window::Controller *Application::activeWindow() const {
|
||||
return _lastActiveWindow;
|
||||
}
|
||||
|
||||
bool Application::closeNonLastAsync(not_null<Window::Controller*> window) {
|
||||
const auto hasOther = [&] {
|
||||
for (const auto &[account, primary] : _primaryWindows) {
|
||||
if (!_closingAsyncWindows.contains(primary.get())
|
||||
&& primary.get() != window
|
||||
&& primary->maybeSession()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
if (!hasOther) {
|
||||
return false;
|
||||
}
|
||||
_closingAsyncWindows.emplace(window);
|
||||
crl::on_main(window, [=] { closeWindow(window); });
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::setLastActiveWindow(Window::Controller *window) {
|
||||
_floatPlayerDelegateLifetime.destroy();
|
||||
|
||||
if (_floatPlayerGifsPaused && _lastActiveWindow) {
|
||||
if (const auto delegate = _lastActiveWindow->floatPlayerDelegate()) {
|
||||
delegate->floatPlayerToggleGifsPaused(false);
|
||||
}
|
||||
}
|
||||
_lastActiveWindow = window;
|
||||
if (!window) {
|
||||
_floatPlayers = nullptr;
|
||||
return;
|
||||
}
|
||||
window->floatPlayerDelegateValue(
|
||||
) | rpl::start_with_next([=](Media::Player::FloatDelegate *value) {
|
||||
if (!value) {
|
||||
_floatPlayers = nullptr;
|
||||
} else if (_floatPlayers) {
|
||||
_floatPlayers->replaceDelegate(value);
|
||||
} else if (value) {
|
||||
_floatPlayers = std::make_unique<Media::Player::FloatController>(
|
||||
value);
|
||||
}
|
||||
if (value && _floatPlayerGifsPaused) {
|
||||
value->floatPlayerToggleGifsPaused(true);
|
||||
}
|
||||
}, _floatPlayerDelegateLifetime);
|
||||
}
|
||||
|
||||
void Application::closeWindow(not_null<Window::Controller*> window) {
|
||||
const auto next = (_primaryWindows.front().second.get() != window)
|
||||
? _primaryWindows.front().second.get()
|
||||
: (_primaryWindows.back().second.get() != window)
|
||||
? _primaryWindows.back().second.get()
|
||||
: nullptr;
|
||||
if (_lastActivePrimaryWindow == window) {
|
||||
_lastActivePrimaryWindow = next;
|
||||
}
|
||||
if (_windowInSettings == window) {
|
||||
_windowInSettings = next;
|
||||
}
|
||||
if (_lastActiveWindow == window) {
|
||||
setLastActiveWindow(next);
|
||||
if (_lastActiveWindow) {
|
||||
_lastActiveWindow->activate();
|
||||
_lastActiveWindow->widget()->updateGlobalMenu();
|
||||
}
|
||||
}
|
||||
_closingAsyncWindows.remove(window);
|
||||
for (auto i = begin(_primaryWindows); i != end(_primaryWindows);) {
|
||||
if (i->second.get() == window) {
|
||||
Assert(_lastActiveWindow != window);
|
||||
Assert(_lastActivePrimaryWindow != window);
|
||||
Assert(_windowInSettings != window);
|
||||
i = _primaryWindows.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
for (auto i = begin(_secondaryWindows); i != end(_secondaryWindows);) {
|
||||
if (i->second.get() == window) {
|
||||
if (_lastActiveWindow == window) {
|
||||
_lastActiveWindow = _primaryWindow.get();
|
||||
}
|
||||
Assert(_lastActiveWindow != window);
|
||||
i = _secondaryWindows.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
const auto account = domain().started()
|
||||
? &domain().active()
|
||||
: nullptr;
|
||||
if (account && !_primaryWindows.contains(account) && _lastActiveWindow) {
|
||||
domain().activate(&_lastActiveWindow->account());
|
||||
}
|
||||
}
|
||||
|
||||
void Application::closeChatFromWindows(not_null<PeerData*> peer) {
|
||||
if (const auto window = windowFor(peer)
|
||||
; window && !window->isPrimary()) {
|
||||
closeWindow(window);
|
||||
}
|
||||
for (const auto &[history, window] : _secondaryWindows) {
|
||||
if (history->peer == peer) {
|
||||
closeWindow(window.get());
|
||||
break;
|
||||
} else if (const auto session = window->sessionController()) {
|
||||
if (const auto session = window->sessionController()) {
|
||||
if (session->activeChatCurrent().peer() == peer) {
|
||||
session->showPeerHistory(
|
||||
window->singlePeer()->id,
|
||||
@@ -1240,8 +1376,8 @@ void Application::closeChatFromWindows(not_null<PeerData*> peer) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_primaryWindow && _primaryWindow->sessionController()) {
|
||||
const auto primary = _primaryWindow->sessionController();
|
||||
if (const auto window = windowFor(&peer->account())) {
|
||||
const auto primary = window->sessionController();
|
||||
if ((primary->activeChatCurrent().peer() == peer)
|
||||
&& (&primary->session() == &peer->session())) {
|
||||
primary->clearSectionStack();
|
||||
@@ -1257,7 +1393,13 @@ void Application::closeChatFromWindows(not_null<PeerData*> peer) {
|
||||
void Application::windowActivated(not_null<Window::Controller*> window) {
|
||||
const auto was = _lastActiveWindow;
|
||||
const auto now = window;
|
||||
_lastActiveWindow = window;
|
||||
|
||||
setLastActiveWindow(window);
|
||||
|
||||
if (window->isPrimary()) {
|
||||
_lastActivePrimaryWindow = window;
|
||||
}
|
||||
window->widget()->updateGlobalMenu();
|
||||
|
||||
const auto wasSession = was ? was->maybeSession() : nullptr;
|
||||
const auto nowSession = now->maybeSession();
|
||||
@@ -1420,8 +1562,8 @@ void Application::quitPreventFinished() {
|
||||
}
|
||||
|
||||
void Application::quitDelayed() {
|
||||
if (_primaryWindow) {
|
||||
_primaryWindow->widget()->hide();
|
||||
for (const auto &[account, window] : _primaryWindows) {
|
||||
window->widget()->hide();
|
||||
}
|
||||
for (const auto &[history, window] : _secondaryWindows) {
|
||||
window->widget()->hide();
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace Player {
|
||||
class FloatController;
|
||||
class FloatDelegate;
|
||||
} // namespace Player
|
||||
class SystemMediaControlsManager;
|
||||
} // namespace Media
|
||||
|
||||
namespace Lang {
|
||||
@@ -152,13 +153,25 @@ public:
|
||||
|
||||
// Windows interface.
|
||||
bool hasActiveWindow(not_null<Main::Session*> session) const;
|
||||
[[nodiscard]] Window::Controller *primaryWindow() const;
|
||||
[[nodiscard]] bool savingPositionFor(
|
||||
not_null<Window::Controller*> window) const;
|
||||
[[nodiscard]] Window::Controller *activeWindow() const;
|
||||
[[nodiscard]] Window::Controller *activePrimaryWindow() const;
|
||||
[[nodiscard]] Window::Controller *separateWindowForAccount(
|
||||
not_null<Main::Account*> account) const;
|
||||
[[nodiscard]] Window::Controller *separateWindowForPeer(
|
||||
not_null<PeerData*> peer) const;
|
||||
Window::Controller *ensureSeparateWindowForPeer(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId showAtMsgId);
|
||||
Window::Controller *ensureSeparateWindowForAccount(
|
||||
not_null<Main::Account*> account);
|
||||
[[nodiscard]] Window::Controller *windowFor( // Doesn't auto-switch.
|
||||
not_null<PeerData*> peer) const;
|
||||
[[nodiscard]] Window::Controller *windowFor( // Doesn't auto-switch.
|
||||
not_null<Main::Account*> account) const;
|
||||
[[nodiscard]] bool closeNonLastAsync(
|
||||
not_null<Window::Controller*> window);
|
||||
void closeWindow(not_null<Window::Controller*> window);
|
||||
void windowActivated(not_null<Window::Controller*> window);
|
||||
bool closeActiveWindow();
|
||||
@@ -168,6 +181,7 @@ public:
|
||||
void checkSystemDarkMode();
|
||||
[[nodiscard]] bool isActiveForTrayMenu() const;
|
||||
void closeChatFromWindows(not_null<PeerData*> peer);
|
||||
void activate();
|
||||
|
||||
// Media view interface.
|
||||
bool hideMediaView();
|
||||
@@ -244,12 +258,7 @@ public:
|
||||
[[nodiscard]] QString changelogLink() const;
|
||||
|
||||
// Float player.
|
||||
void setDefaultFloatPlayerDelegate(
|
||||
not_null<Media::Player::FloatDelegate*> delegate);
|
||||
void replaceFloatPlayerDelegate(
|
||||
not_null<Media::Player::FloatDelegate*> replacement);
|
||||
void restoreFloatPlayerDelegate(
|
||||
not_null<Media::Player::FloatDelegate*> replacement);
|
||||
void floatPlayerToggleGifsPaused(bool paused);
|
||||
[[nodiscard]] rpl::producer<FullMsgId> floatPlayerClosed() const;
|
||||
|
||||
// Calls.
|
||||
@@ -325,9 +334,11 @@ private:
|
||||
void startSystemDarkModeViewer();
|
||||
void startTray();
|
||||
|
||||
void setLastActiveWindow(Window::Controller *window);
|
||||
void showAccount(not_null<Main::Account*> account);
|
||||
void enumerateWindows(
|
||||
Fn<void(not_null<Window::Controller*>)> callback) const;
|
||||
void processSecondaryWindow(not_null<Window::Controller*> window);
|
||||
void processCreatedWindow(not_null<Window::Controller*> window);
|
||||
|
||||
friend void QuitAttempt();
|
||||
void quitDelayed();
|
||||
@@ -371,15 +382,22 @@ private:
|
||||
// Mutable because is created in run() after OpenSSL is inited.
|
||||
std::unique_ptr<Window::Notifications::System> _notifications;
|
||||
|
||||
using MediaControlsManager = Media::SystemMediaControlsManager;
|
||||
std::unique_ptr<MediaControlsManager> _mediaControlsManager;
|
||||
const std::unique_ptr<Data::DownloadManager> _downloadManager;
|
||||
const std::unique_ptr<Main::Domain> _domain;
|
||||
const std::unique_ptr<Export::Manager> _exportManager;
|
||||
const std::unique_ptr<Calls::Instance> _calls;
|
||||
std::unique_ptr<Window::Controller> _primaryWindow;
|
||||
base::flat_map<
|
||||
Main::Account*,
|
||||
std::unique_ptr<Window::Controller>> _primaryWindows;
|
||||
base::flat_set<not_null<Window::Controller*>> _closingAsyncWindows;
|
||||
base::flat_map<
|
||||
not_null<History*>,
|
||||
std::unique_ptr<Window::Controller>> _secondaryWindows;
|
||||
Window::Controller *_lastActiveWindow = nullptr;
|
||||
Window::Controller *_lastActivePrimaryWindow = nullptr;
|
||||
Window::Controller *_windowInSettings = nullptr;
|
||||
|
||||
std::unique_ptr<Media::View::OverlayWidget> _mediaView;
|
||||
const std::unique_ptr<Lang::Instance> _langpack;
|
||||
@@ -391,8 +409,8 @@ private:
|
||||
const std::unique_ptr<Tray> _tray;
|
||||
|
||||
std::unique_ptr<Media::Player::FloatController> _floatPlayers;
|
||||
Media::Player::FloatDelegate *_defaultFloatPlayerDelegate = nullptr;
|
||||
Media::Player::FloatDelegate *_replacementFloatPlayerDelegate = nullptr;
|
||||
rpl::lifetime _floatPlayerDelegateLifetime;
|
||||
bool _floatPlayerGifsPaused = false;
|
||||
|
||||
rpl::variable<bool> _passcodeLock;
|
||||
bool _screenIsLocked = false;
|
||||
|
||||
@@ -22,61 +22,6 @@ namespace {
|
||||
|
||||
std::map<int, const char*> BetaLogs() {
|
||||
return {
|
||||
{
|
||||
4000003,
|
||||
"- Animated emoji for messages.\n"
|
||||
|
||||
"- Premium: Privacy settings for voice messages.\n"
|
||||
|
||||
"- Premium: Gifting Telegram Premium "
|
||||
"to any user from their profile page.\n"
|
||||
},
|
||||
{
|
||||
4000004,
|
||||
"- Allow sending animated emoji to Saved Messages "
|
||||
"even without Telegram Premium.\n"
|
||||
|
||||
"- Premium: Suggest animated emoji by regular emoji "
|
||||
"(can be disabled in Settings).\n"
|
||||
|
||||
"- Premium: Show all suggested premium stickers "
|
||||
"in a special section of the stickers panel.\n"
|
||||
|
||||
"- Premium: Allow hiding premium stickers special section "
|
||||
"of the stickers panel.\n"
|
||||
|
||||
"- Fix a memory leak in RTMP livestreams.\n"
|
||||
|
||||
"- Fix some bot webview bugs on macOS.\n"
|
||||
|
||||
"- Fix forwarding of voice messages.\n"
|
||||
},
|
||||
{
|
||||
4001002,
|
||||
"- New reaction selector above the right click menu.\n"
|
||||
|
||||
"- Premium: Set any custom emoji reactions in private chats.\n"
|
||||
|
||||
"- Premium: Set any custom emoji as your profile status.\n"
|
||||
|
||||
"- Insert or copy custom emoji from pack preview.\n"
|
||||
},
|
||||
{
|
||||
4002001,
|
||||
"- Improve scaling / cropping for photos / video files.\n"
|
||||
|
||||
"- Improve touch support in channel comments.\n"
|
||||
|
||||
"- Nice animation for spoilers.\n"
|
||||
},
|
||||
{
|
||||
4002002,
|
||||
"- Fix crash in spoiler revealing in media captions.\n"
|
||||
|
||||
"- Fix spoiler revealing in media viewer captions.\n"
|
||||
|
||||
"- Fix crash in folder editing on Linux.\n"
|
||||
},
|
||||
{
|
||||
4004002,
|
||||
"- Send photos and video files hidden by a spoiler effect.\n"
|
||||
@@ -93,6 +38,43 @@ std::map<int, const char*> BetaLogs() {
|
||||
"- Fix a crash in own profile photo updating.\n"
|
||||
|
||||
"- Bug fixes and other minor improvements.\n"
|
||||
},
|
||||
{
|
||||
4005004,
|
||||
"- Allow wide range of interface scale options.\n"
|
||||
|
||||
"- Show opened chat name in the window title.\n"
|
||||
|
||||
"- Bug fixes and other minor improvements.\n"
|
||||
|
||||
"- Fix updating on macOS older than 10.14.\n"
|
||||
},
|
||||
{
|
||||
4005006,
|
||||
"- Try enabling non-fractional scale "
|
||||
"High DPI support on Windows and Linux.\n"
|
||||
|
||||
"- Experimental setting for fractional scale "
|
||||
"High DPI support on Windows and Linux.\n"
|
||||
|
||||
"- Fix navigation to bottom problems in groups you didn't join.\n"
|
||||
|
||||
"- Fix a crash in chat export settings changes.\n"
|
||||
|
||||
"- Fix a crash in sending some of JPEG images.\n"
|
||||
|
||||
"- Fix CJK fonts on Windows.\n"
|
||||
},
|
||||
{
|
||||
4005007,
|
||||
"- Fix glitches after moving window to another screen.\n",
|
||||
},
|
||||
{
|
||||
4005008,
|
||||
"- Allow opening another account in a new window "
|
||||
"(see Settings > Advanced > Experimental Settings).\n"
|
||||
|
||||
"- A lot of bugfixes for working with more than one window.\n"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -93,7 +93,8 @@ QString HiddenUrlClickHandler::copyToClipboardContextItemText() const {
|
||||
}
|
||||
|
||||
QString HiddenUrlClickHandler::dragText() const {
|
||||
return HiddenUrlClickHandler::copyToClipboardText();
|
||||
const auto result = HiddenUrlClickHandler::copyToClipboardText();
|
||||
return result.startsWith(u"internal:"_q) ? QString() : result;
|
||||
}
|
||||
|
||||
void HiddenUrlClickHandler::Open(QString url, QVariant context) {
|
||||
|
||||
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/zlib_help.h"
|
||||
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtGui/QFontInfo>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtCore/QStandardPaths>
|
||||
@@ -43,12 +44,37 @@ PreLaunchWindow::PreLaunchWindow(QString title) {
|
||||
p.setColor(QPalette::Window, QColor(255, 255, 255));
|
||||
setPalette(p);
|
||||
|
||||
_size = QFontMetrics(font()).height();
|
||||
_size = QFontInfo(font()).pixelSize();
|
||||
|
||||
int paddingVertical = (_size / 2);
|
||||
int paddingHorizontal = _size;
|
||||
int borderRadius = (_size / 5);
|
||||
setStyleSheet(u"QPushButton { padding: %1px %2px; background-color: #ffffff; border-radius: %3px; }\nQPushButton#confirm:hover, QPushButton#cancel:hover { background-color: #e3f1fa; color: #2f9fea; }\nQPushButton#confirm { color: #2f9fea; }\nQPushButton#cancel { color: #aeaeae; }\nQLineEdit { border: 1px solid #e0e0e0; padding: 5px; }\nQLineEdit:focus { border: 2px solid #37a1de; padding: 4px; }"_q.arg(paddingVertical).arg(paddingHorizontal).arg(borderRadius));
|
||||
setStyleSheet(uR"(
|
||||
QPushButton {
|
||||
padding: %1px %2px;
|
||||
background-color: #ffffff;
|
||||
border-radius: %3px;
|
||||
}
|
||||
QPushButton#confirm:hover,
|
||||
QPushButton#cancel:hover {
|
||||
background-color: #e3f1fa;
|
||||
color: #2f9fea;
|
||||
}
|
||||
QPushButton#confirm {
|
||||
color: #2f9fea;
|
||||
}
|
||||
QPushButton#cancel {
|
||||
color: #aeaeae;
|
||||
}
|
||||
QLineEdit {
|
||||
border: 1px solid #e0e0e0;
|
||||
padding: 5px;
|
||||
}
|
||||
QLineEdit:focus {
|
||||
border: 2px solid #37a1de;
|
||||
padding: 4px;
|
||||
}
|
||||
)"_q.arg(paddingVertical).arg(paddingHorizontal).arg(borderRadius));
|
||||
if (!PreLaunchWindowInstance) {
|
||||
PreLaunchWindowInstance = this;
|
||||
}
|
||||
@@ -57,7 +83,7 @@ PreLaunchWindow::PreLaunchWindow(QString title) {
|
||||
void PreLaunchWindow::activate() {
|
||||
setWindowState(windowState() & ~Qt::WindowMinimized);
|
||||
setVisible(true);
|
||||
psActivateProcess();
|
||||
Platform::ActivateThisProcess();
|
||||
raise();
|
||||
activateWindow();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/base_platform_file_utilities.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
@@ -75,7 +76,7 @@ QString filedialogDefaultName(
|
||||
QString base;
|
||||
if (fileTime) {
|
||||
const auto date = base::unixtime::parse(fileTime);
|
||||
base = prefix + QLocale().toString(date, "_yyyy-MM-dd_HH-mm-ss");
|
||||
base = prefix + date.toString("_yyyy-MM-dd_HH-mm-ss");
|
||||
} else {
|
||||
struct tm tm;
|
||||
time_t t = time(NULL);
|
||||
@@ -137,9 +138,9 @@ void OpenEmailLink(const QString &email) {
|
||||
});
|
||||
}
|
||||
|
||||
void OpenWith(const QString &filepath, QPoint menuPosition) {
|
||||
void OpenWith(const QString &filepath) {
|
||||
InvokeQueued(QCoreApplication::instance(), [=] {
|
||||
if (!Platform::File::UnsafeShowOpenWithDropdown(filepath, menuPosition)) {
|
||||
if (!Platform::File::UnsafeShowOpenWithDropdown(filepath)) {
|
||||
Ui::PreventDelayedActivation();
|
||||
if (!Platform::File::UnsafeShowOpenWith(filepath)) {
|
||||
Platform::File::UnsafeLaunch(filepath);
|
||||
@@ -171,31 +172,14 @@ QString DefaultDownloadPathFolder(not_null<Main::Session*> session) {
|
||||
}
|
||||
|
||||
QString DefaultDownloadPath(not_null<Main::Session*> session) {
|
||||
const auto realDefaultPath = QStandardPaths::writableLocation(
|
||||
if (!Core::App().canReadDefaultDownloadPath()) {
|
||||
return session->local().tempDirectory();
|
||||
}
|
||||
return QStandardPaths::writableLocation(
|
||||
QStandardPaths::DownloadLocation)
|
||||
+ '/'
|
||||
+ DefaultDownloadPathFolder(session)
|
||||
+ '/';
|
||||
if (!Core::App().canReadDefaultDownloadPath()) {
|
||||
QStringList files;
|
||||
QByteArray remoteContent;
|
||||
const auto success = Platform::FileDialog::Get(
|
||||
nullptr,
|
||||
files,
|
||||
remoteContent,
|
||||
tr::lng_download_path_choose(tr::now),
|
||||
QString(),
|
||||
FileDialog::internal::Type::ReadFolder,
|
||||
realDefaultPath);
|
||||
if (success && !files.isEmpty() && !files[0].isEmpty()) {
|
||||
const auto result = files[0].endsWith('/') ? files[0] : (files[0] + '/');
|
||||
Core::App().settings().setDownloadPath(result);
|
||||
Core::App().saveSettings();
|
||||
return result;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
return realDefaultPath;
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace File {
|
||||
// Those functions are async wrappers to Platform::File::Unsafe* calls.
|
||||
void OpenUrl(const QString &url);
|
||||
void OpenEmailLink(const QString &email);
|
||||
void OpenWith(const QString &filepath, QPoint menuPosition);
|
||||
void OpenWith(const QString &filepath);
|
||||
void Launch(const QString &filepath);
|
||||
void ShowInFolder(const QString &filepath);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "platform/platform_launcher.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/options.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/base_platform_file_utilities.h"
|
||||
#include "ui/main_queue_processor.h"
|
||||
@@ -272,8 +273,18 @@ bool CheckPortableVersionFolder() {
|
||||
return true;
|
||||
}
|
||||
|
||||
base::options::toggle OptionFractionalScalingEnabled({
|
||||
.id = kOptionFractionalScalingEnabled,
|
||||
.name = "Enable precise High DPI scaling",
|
||||
.description = "Follow system interface scale settings exactly.",
|
||||
.scope = base::options::windows | base::options::linux,
|
||||
.restartRequired = true,
|
||||
});
|
||||
|
||||
} // namespace
|
||||
|
||||
const char kOptionFractionalScalingEnabled[] = "fractional-scaling-enabled";
|
||||
|
||||
std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) {
|
||||
return std::make_unique<Platform::Launcher>(argc, argv);
|
||||
}
|
||||
@@ -294,9 +305,6 @@ void Launcher::init() {
|
||||
initQtMessageLogging();
|
||||
|
||||
QApplication::setApplicationName(u"TelegramDesktop"_q);
|
||||
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::Floor);
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
// fallback session management is useless for tdesktop since it doesn't have
|
||||
@@ -311,6 +319,17 @@ void Launcher::init() {
|
||||
initHook();
|
||||
}
|
||||
|
||||
void Launcher::initHighDpi() {
|
||||
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
|
||||
if (OptionFractionalScalingEnabled.value()) {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
} else {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor);
|
||||
}
|
||||
}
|
||||
|
||||
int Launcher::exec() {
|
||||
init();
|
||||
|
||||
@@ -324,6 +343,9 @@ int Launcher::exec() {
|
||||
Logs::start(this);
|
||||
base::options::init(cWorkingDir() + "tdata/experimental_options.json");
|
||||
|
||||
// Must be called after options are inited.
|
||||
initHighDpi();
|
||||
|
||||
if (Logs::DebugEnabled()) {
|
||||
const auto openalLogPath = QDir::toNativeSeparators(
|
||||
cWorkingDir() + u"DebugLogs/last_openal_log.txt"_q);
|
||||
|
||||
@@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Core {
|
||||
|
||||
extern const char kOptionFractionalScalingEnabled[];
|
||||
|
||||
class Launcher {
|
||||
public:
|
||||
Launcher(int argc, char *argv[]);
|
||||
@@ -52,6 +54,7 @@ private:
|
||||
void init();
|
||||
virtual void initHook() {
|
||||
}
|
||||
virtual void initHighDpi();
|
||||
|
||||
virtual bool launchUpdater(UpdaterLaunch action) = 0;
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "settings/settings_premium.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "inline_bots/bot_attach_web_view.h"
|
||||
@@ -798,12 +799,13 @@ bool ResolveLoginCode(
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
const auto loginCode = match->captured(2);
|
||||
if (loginCode.isEmpty()) {
|
||||
const auto &domain = Core::App().domain();
|
||||
if (loginCode.isEmpty() || (!controller && !domain.started())) {
|
||||
return false;
|
||||
};
|
||||
(controller
|
||||
? controller->session().account()
|
||||
: Core::App().activeAccount()).handleLoginCode(loginCode);
|
||||
: domain.active()).handleLoginCode(loginCode);
|
||||
if (controller) {
|
||||
controller->window().activate();
|
||||
} else if (const auto window = Core::App().activeWindow()) {
|
||||
@@ -869,7 +871,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
ResolvePrivatePost
|
||||
},
|
||||
{
|
||||
u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information)?$"_q,
|
||||
u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information|/edit_profile)?$"_q,
|
||||
ResolveSettings
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/mime_type.h"
|
||||
|
||||
#include "core/utils.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
|
||||
#include <QtCore/QMimeDatabase>
|
||||
#include <QtCore/QMimeData>
|
||||
@@ -164,10 +165,31 @@ std::shared_ptr<QMimeData> ShareMimeMediaData(
|
||||
if (original->hasImage()) {
|
||||
result->setImageData(original->imageData());
|
||||
}
|
||||
if (original->hasFormat(u"application/x-td-use-jpeg"_q)
|
||||
&& original->hasFormat(u"image/jpeg"_q)) {
|
||||
result->setData(u"application/x-td-use-jpeg"_q, "1");
|
||||
result->setData(u"image/jpeg"_q, original->data(u"image/jpeg"_q));
|
||||
}
|
||||
if (auto list = base::GetMimeUrls(original); !list.isEmpty()) {
|
||||
result->setUrls(std::move(list));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
MimeImageData ReadMimeImage(not_null<const QMimeData*> data) {
|
||||
if (data->hasFormat(u"application/x-td-use-jpeg"_q)) {
|
||||
auto bytes = data->data(u"image/jpeg"_q);
|
||||
auto read = Images::Read({ .content = bytes });
|
||||
if (read.format == "jpeg" && !read.image.isNull()) {
|
||||
return {
|
||||
.image = std::move(read.image),
|
||||
.content = std::move(bytes),
|
||||
};
|
||||
}
|
||||
} else if (data->hasImage()) {
|
||||
return { .image = qvariant_cast<QImage>(data->imageData()) };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QMimeType>
|
||||
#include <QtGui/QImage>
|
||||
|
||||
class QMimeData;
|
||||
|
||||
@@ -52,4 +53,17 @@ private:
|
||||
[[nodiscard]] std::shared_ptr<QMimeData> ShareMimeMediaData(
|
||||
not_null<const QMimeData*> original);
|
||||
|
||||
struct MimeImageData {
|
||||
QImage image;
|
||||
QByteArray content;
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return image.isNull();
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return !empty();
|
||||
}
|
||||
};
|
||||
[[nodiscard]] MimeImageData ReadMimeImage(not_null<const QMimeData*> data);
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -25,19 +25,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
#include <QtCore/QLockFile>
|
||||
#include <QtGui/QSessionManager>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtGui/qpa/qplatformscreen.h>
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
constexpr auto kEmptyPidForCommandResponse = 0ULL;
|
||||
|
||||
QChar _toHex(ushort v) {
|
||||
v = v & 0x000F;
|
||||
return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v));
|
||||
@@ -134,7 +132,7 @@ int Sandbox::start() {
|
||||
[=] { socketDisconnected(); });
|
||||
connect(
|
||||
&_localSocket,
|
||||
base::QLocalSocket_error,
|
||||
&QLocalSocket::errorOccurred,
|
||||
[=](QLocalSocket::LocalSocketError error) { socketError(error); });
|
||||
connect(
|
||||
&_localSocket,
|
||||
@@ -210,38 +208,52 @@ void Sandbox::launchApplication() {
|
||||
}
|
||||
|
||||
void Sandbox::setupScreenScale() {
|
||||
const auto dpi = Sandbox::primaryScreen()->logicalDotsPerInch();
|
||||
LOG(("Primary screen DPI: %1").arg(dpi));
|
||||
if (dpi <= 108) {
|
||||
cSetScreenScale(100); // 100%: 96 DPI (0-108)
|
||||
} else if (dpi <= 132) {
|
||||
cSetScreenScale(125); // 125%: 120 DPI (108-132)
|
||||
} else if (dpi <= 168) {
|
||||
cSetScreenScale(150); // 150%: 144 DPI (132-168)
|
||||
} else if (dpi <= 216) {
|
||||
cSetScreenScale(200); // 200%: 192 DPI (168-216)
|
||||
} else if (dpi <= 264) {
|
||||
cSetScreenScale(250); // 250%: 240 DPI (216-264)
|
||||
} else {
|
||||
cSetScreenScale(300); // 300%: 288 DPI (264-inf)
|
||||
}
|
||||
|
||||
const auto ratio = devicePixelRatio();
|
||||
if (ratio > 1.) {
|
||||
if (!Platform::IsMac() || (ratio != 2.)) {
|
||||
LOG(("Found non-trivial Device Pixel Ratio: %1").arg(ratio));
|
||||
LOG(("Environmental variables: QT_DEVICE_PIXEL_RATIO='%1'").arg(qEnvironmentVariable("QT_DEVICE_PIXEL_RATIO")));
|
||||
LOG(("Environmental variables: QT_SCALE_FACTOR='%1'").arg(qEnvironmentVariable("QT_SCALE_FACTOR")));
|
||||
LOG(("Environmental variables: QT_AUTO_SCREEN_SCALE_FACTOR='%1'").arg(qEnvironmentVariable("QT_AUTO_SCREEN_SCALE_FACTOR")));
|
||||
LOG(("Environmental variables: QT_SCREEN_SCALE_FACTORS='%1'").arg(qEnvironmentVariable("QT_SCREEN_SCALE_FACTORS")));
|
||||
}
|
||||
style::SetDevicePixelRatio(std::ceil(ratio));
|
||||
if (Platform::IsMac() && ratio == 2.) {
|
||||
cSetScreenScale(110); // 110% for Retina screens by default.
|
||||
} else {
|
||||
cSetScreenScale(style::kScaleDefault);
|
||||
LOG(("Global devicePixelRatio: %1").arg(ratio));
|
||||
const auto logEnv = [](const char *name) {
|
||||
const auto value = qEnvironmentVariable(name);
|
||||
if (!value.isEmpty()) {
|
||||
LOG(("%1: %2").arg(name).arg(value));
|
||||
}
|
||||
};
|
||||
logEnv("QT_DEVICE_PIXEL_RATIO");
|
||||
logEnv("QT_AUTO_SCREEN_SCALE_FACTOR");
|
||||
logEnv("QT_ENABLE_HIGHDPI_SCALING");
|
||||
logEnv("QT_SCALE_FACTOR");
|
||||
logEnv("QT_SCREEN_SCALE_FACTORS");
|
||||
logEnv("QT_SCALE_FACTOR_ROUNDING_POLICY");
|
||||
logEnv("QT_DPI_ADJUSTMENT_POLICY");
|
||||
logEnv("QT_USE_PHYSICAL_DPI");
|
||||
logEnv("QT_FONT_DPI");
|
||||
|
||||
// Like Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor.
|
||||
// Round up for .75 and higher. This favors "small UI" over "large UI".
|
||||
const auto roundedRatio = ((ratio - qFloor(ratio)) < 0.75)
|
||||
? qFloor(ratio)
|
||||
: qCeil(ratio);
|
||||
const auto useRatio = std::clamp(roundedRatio, 1, 3);
|
||||
style::SetDevicePixelRatio(useRatio);
|
||||
|
||||
const auto screen = Sandbox::primaryScreen();
|
||||
const auto dpi = screen->logicalDotsPerInch();
|
||||
const auto basePair = screen->handle()->logicalBaseDpi();
|
||||
const auto base = (basePair.first + basePair.second) * 0.5;
|
||||
const auto screenScaleExact = dpi / base;
|
||||
const auto screenScale = int(base::SafeRound(screenScaleExact * 4)) * 25;
|
||||
LOG(("Primary screen DPI: %1, Base: %2.").arg(dpi).arg(base));
|
||||
LOG(("Computed screen scale: %1").arg(screenScale));
|
||||
if (Platform::IsMac()) {
|
||||
// 110% for Retina screens by default.
|
||||
cSetScreenScale((useRatio == 2) ? 110 : 100);
|
||||
} else {
|
||||
const auto clamped = std::clamp(
|
||||
screenScale * useRatio,
|
||||
50 * useRatio,
|
||||
300);
|
||||
cSetScreenScale(int(base::SafeRound(clamped * 1. / useRatio)));
|
||||
}
|
||||
LOG(("DevicePixelRatio: %1").arg(useRatio));
|
||||
LOG(("ScreenScale: %1").arg(cScreenScale()));
|
||||
}
|
||||
|
||||
Sandbox::~Sandbox() = default;
|
||||
@@ -298,17 +310,21 @@ void Sandbox::socketReading() {
|
||||
return;
|
||||
}
|
||||
_localSocketReadData.append(_localSocket.readAll());
|
||||
if (QRegularExpression("RES:(\\d+);").match(_localSocketReadData).hasMatch()) {
|
||||
uint64 pid = base::StringViewMid(
|
||||
_localSocketReadData,
|
||||
4,
|
||||
_localSocketReadData.length() - 5).toULongLong();
|
||||
if (pid != kEmptyPidForCommandResponse) {
|
||||
psActivateProcess(pid);
|
||||
}
|
||||
LOG(("Show command response received, pid = %1, activating and quitting...").arg(pid));
|
||||
return Quit();
|
||||
const auto m = QRegularExpression(u"RES:(\\d+)_(\\d+);"_q).match(
|
||||
_localSocketReadData);
|
||||
if (!m.hasMatch()) {
|
||||
return;
|
||||
}
|
||||
const auto processId = m.capturedView(1).toULongLong();
|
||||
const auto windowId = m.capturedView(2).toULongLong();
|
||||
if (windowId) {
|
||||
Platform::ActivateOtherProcess(processId, windowId);
|
||||
}
|
||||
LOG(("Show command response received, processId = %1, windowId = %2, "
|
||||
"activating and quitting..."
|
||||
).arg(processId
|
||||
).arg(windowId));
|
||||
return Quit();
|
||||
}
|
||||
|
||||
void Sandbox::socketError(QLocalSocket::LocalSocketError e) {
|
||||
@@ -426,8 +442,9 @@ void Sandbox::readClients() {
|
||||
for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; to = (from < l) ? cmds.indexOf(QChar(';'), from) : -1) {
|
||||
auto cmd = base::StringViewMid(cmds, from, to - from);
|
||||
if (cmd.startsWith(u"CMD:"_q)) {
|
||||
execExternal(cmds.mid(from + 4, to - from - 4));
|
||||
const auto response = u"RES:%1;"_q.arg(QApplication::applicationPid()).toLatin1();
|
||||
const auto processId = QApplication::applicationPid();
|
||||
const auto windowId = execExternal(cmds.mid(from + 4, to - from - 4));
|
||||
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
|
||||
i->first->write(response.data(), response.size());
|
||||
} else if (cmd.startsWith(u"SEND:"_q)) {
|
||||
if (cSendPaths().isEmpty()) {
|
||||
@@ -437,14 +454,12 @@ void Sandbox::readClients() {
|
||||
qputenv("XDG_ACTIVATION_TOKEN", _escapeFrom7bit(cmds.mid(from + 21, to - from - 21)).toUtf8());
|
||||
} else if (cmd.startsWith(u"OPEN:"_q)) {
|
||||
startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192);
|
||||
auto activateRequired = StartUrlRequiresActivate(startUrl);
|
||||
if (activateRequired) {
|
||||
execExternal("show");
|
||||
}
|
||||
const auto responsePid = activateRequired
|
||||
? QApplication::applicationPid()
|
||||
: kEmptyPidForCommandResponse;
|
||||
const auto response = u"RES:%1;"_q.arg(responsePid).toLatin1();
|
||||
const auto activationRequired = StartUrlRequiresActivate(startUrl);
|
||||
const auto processId = QApplication::applicationPid();
|
||||
const auto windowId = activationRequired
|
||||
? execExternal("show")
|
||||
: 0;
|
||||
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
|
||||
i->first->write(response.data(), response.size());
|
||||
} else {
|
||||
LOG(("Sandbox Error: unknown command %1 passed in local socket").arg(cmd.toString()));
|
||||
@@ -643,17 +658,21 @@ void Sandbox::closeApplication() {
|
||||
_updateChecker = nullptr;
|
||||
}
|
||||
|
||||
void Sandbox::execExternal(const QString &cmd) {
|
||||
uint64 Sandbox::execExternal(const QString &cmd) {
|
||||
DEBUG_LOG(("Sandbox Info: executing external command '%1'").arg(cmd));
|
||||
if (cmd == "show") {
|
||||
if (Core::IsAppLaunched() && Core::App().primaryWindow()) {
|
||||
Core::App().primaryWindow()->activate();
|
||||
} else if (PreLaunchWindow::instance()) {
|
||||
PreLaunchWindow::instance()->activate();
|
||||
if (Core::IsAppLaunched() && Core::App().activePrimaryWindow()) {
|
||||
const auto window = Core::App().activePrimaryWindow();
|
||||
window->activate();
|
||||
return Platform::ActivationWindowId(window->widget());
|
||||
} else if (const auto window = PreLaunchWindow::instance()) {
|
||||
window->activate();
|
||||
return Platform::ActivationWindowId(window);
|
||||
}
|
||||
} else if (cmd == "quit") {
|
||||
Quit();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -95,7 +95,9 @@ private:
|
||||
void singleInstanceChecked();
|
||||
void launchApplication();
|
||||
void setupScreenScale();
|
||||
void execExternal(const QString &cmd);
|
||||
|
||||
// Return window id for activation.
|
||||
uint64 execExternal(const QString &cmd);
|
||||
|
||||
// Single instance application
|
||||
void socketConnected();
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/parse_helper.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QShortcut>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
@@ -143,11 +144,13 @@ public:
|
||||
void fill();
|
||||
void clear();
|
||||
|
||||
[[nodiscard]] std::vector<Command> lookup(int shortcutId) const;
|
||||
[[nodiscard]] std::vector<Command> lookup(
|
||||
not_null<QObject*> object) const;
|
||||
void toggleMedia(bool toggled);
|
||||
void toggleSupport(bool toggled);
|
||||
void listen(not_null<QWidget*> widget);
|
||||
|
||||
const QStringList &errors() const;
|
||||
[[nodiscard]] const QStringList &errors() const;
|
||||
|
||||
private:
|
||||
void fillDefaults();
|
||||
@@ -156,15 +159,15 @@ private:
|
||||
|
||||
void set(const QString &keys, Command command, bool replace = false);
|
||||
void remove(const QString &keys);
|
||||
void unregister(base::unique_qptr<QShortcut> shortcut);
|
||||
void unregister(base::unique_qptr<QAction> shortcut);
|
||||
|
||||
QStringList _errors;
|
||||
|
||||
base::flat_map<QKeySequence, base::unique_qptr<QShortcut>> _shortcuts;
|
||||
base::flat_multi_map<int, Command> _commandByShortcutId;
|
||||
base::flat_map<QKeySequence, base::unique_qptr<QAction>> _shortcuts;
|
||||
base::flat_multi_map<not_null<QObject*>, Command> _commandByObject;
|
||||
|
||||
base::flat_set<QShortcut*> _mediaShortcuts;
|
||||
base::flat_set<QShortcut*> _supportShortcuts;
|
||||
base::flat_set<QAction*> _mediaShortcuts;
|
||||
base::flat_set<QAction*> _supportShortcuts;
|
||||
|
||||
};
|
||||
|
||||
@@ -227,7 +230,7 @@ void Manager::fill() {
|
||||
void Manager::clear() {
|
||||
_errors.clear();
|
||||
_shortcuts.clear();
|
||||
_commandByShortcutId.clear();
|
||||
_commandByObject.clear();
|
||||
_mediaShortcuts.clear();
|
||||
_supportShortcuts.clear();
|
||||
}
|
||||
@@ -236,11 +239,11 @@ const QStringList &Manager::errors() const {
|
||||
return _errors;
|
||||
}
|
||||
|
||||
std::vector<Command> Manager::lookup(int shortcutId) const {
|
||||
std::vector<Command> Manager::lookup(not_null<QObject*> object) const {
|
||||
auto result = std::vector<Command>();
|
||||
auto i = _commandByShortcutId.findFirst(shortcutId);
|
||||
const auto end = _commandByShortcutId.end();
|
||||
for (; i != end && (i->first == shortcutId); ++i) {
|
||||
auto i = _commandByObject.findFirst(object);
|
||||
const auto end = _commandByObject.end();
|
||||
for (; i != end && (i->first == object); ++i) {
|
||||
result.push_back(i->second);
|
||||
}
|
||||
return result;
|
||||
@@ -258,6 +261,12 @@ void Manager::toggleSupport(bool toggled) {
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::listen(not_null<QWidget*> widget) {
|
||||
for (const auto &[keys, shortcut] : _shortcuts) {
|
||||
widget->addAction(shortcut.get());
|
||||
}
|
||||
}
|
||||
|
||||
bool Manager::readCustomFile() {
|
||||
// read custom shortcuts from file if it exists or write an empty custom shortcuts file
|
||||
QFile file(CustomFilePath());
|
||||
@@ -370,6 +379,9 @@ void Manager::fillDefaults() {
|
||||
set(u"ctrl+3"_q, Command::ChatPinned3);
|
||||
set(u"ctrl+4"_q, Command::ChatPinned4);
|
||||
set(u"ctrl+5"_q, Command::ChatPinned5);
|
||||
set(u"ctrl+6"_q, Command::ChatPinned6);
|
||||
set(u"ctrl+7"_q, Command::ChatPinned7);
|
||||
set(u"ctrl+8"_q, Command::ChatPinned8);
|
||||
|
||||
auto &&folders = ranges::views::zip(
|
||||
kShowFolder,
|
||||
@@ -409,10 +421,10 @@ void Manager::writeDefaultFile() {
|
||||
shortcuts.push_back(version);
|
||||
|
||||
for (const auto &[sequence, shortcut] : _shortcuts) {
|
||||
const auto shortcutId = shortcut->id();
|
||||
auto i = _commandByShortcutId.findFirst(shortcutId);
|
||||
const auto end = _commandByShortcutId.end();
|
||||
for (; i != end && i->first == shortcutId; ++i) {
|
||||
const auto object = shortcut.get();
|
||||
auto i = _commandByObject.findFirst(object);
|
||||
const auto end = _commandByObject.end();
|
||||
for (; i != end && i->first == object; ++i) {
|
||||
const auto j = CommandNames.find(i->second);
|
||||
if (j != CommandNames.end()) {
|
||||
QJsonObject entry;
|
||||
@@ -438,12 +450,9 @@ void Manager::set(const QString &keys, Command command, bool replace) {
|
||||
_errors.push_back(u"Could not derive key sequence '%1'!"_q.arg(keys));
|
||||
return;
|
||||
}
|
||||
auto shortcut = base::make_unique_q<QShortcut>(
|
||||
result,
|
||||
Core::App().primaryWindow()->widget().get(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
Qt::ApplicationShortcut);
|
||||
auto shortcut = base::make_unique_q<QAction>();
|
||||
shortcut->setShortcut(result);
|
||||
shortcut->setShortcutContext(Qt::ApplicationShortcut);
|
||||
if (!AutoRepeatCommands.contains(command)) {
|
||||
shortcut->setAutoRepeat(false);
|
||||
}
|
||||
@@ -452,20 +461,16 @@ void Manager::set(const QString &keys, Command command, bool replace) {
|
||||
if (isMediaShortcut || isSupportShortcut) {
|
||||
shortcut->setEnabled(false);
|
||||
}
|
||||
auto id = shortcut->id();
|
||||
auto object = shortcut.get();
|
||||
auto i = _shortcuts.find(result);
|
||||
if (i == end(_shortcuts)) {
|
||||
i = _shortcuts.emplace(result, std::move(shortcut)).first;
|
||||
} else if (replace) {
|
||||
unregister(std::exchange(i->second, std::move(shortcut)));
|
||||
} else {
|
||||
id = i->second->id();
|
||||
object = i->second.get();
|
||||
}
|
||||
if (!id) {
|
||||
_errors.push_back(u"Could not create shortcut '%1'!"_q.arg(keys));
|
||||
return;
|
||||
}
|
||||
_commandByShortcutId.emplace(id, command);
|
||||
_commandByObject.emplace(object, command);
|
||||
if (!shortcut && isMediaShortcut) {
|
||||
_mediaShortcuts.emplace(i->second.get());
|
||||
}
|
||||
@@ -491,9 +496,9 @@ void Manager::remove(const QString &keys) {
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::unregister(base::unique_qptr<QShortcut> shortcut) {
|
||||
void Manager::unregister(base::unique_qptr<QAction> shortcut) {
|
||||
if (shortcut) {
|
||||
_commandByShortcutId.erase(shortcut->id());
|
||||
_commandByObject.erase(shortcut.get());
|
||||
_mediaShortcuts.erase(shortcut.get());
|
||||
_supportShortcuts.erase(shortcut.get());
|
||||
}
|
||||
@@ -557,8 +562,10 @@ const QStringList &Errors() {
|
||||
return Data.errors();
|
||||
}
|
||||
|
||||
bool HandleEvent(not_null<QShortcutEvent*> event) {
|
||||
return Launch(Data.lookup(event->shortcutId()));
|
||||
bool HandleEvent(
|
||||
not_null<QObject*> object,
|
||||
not_null<QShortcutEvent*> event) {
|
||||
return Launch(Data.lookup(object));
|
||||
}
|
||||
|
||||
void ToggleMediaShortcuts(bool toggled) {
|
||||
@@ -573,4 +580,8 @@ void Finish() {
|
||||
Data.clear();
|
||||
}
|
||||
|
||||
void Listen(not_null<QWidget*> widget) {
|
||||
Data.listen(widget);
|
||||
}
|
||||
|
||||
} // namespace Shortcuts
|
||||
|
||||
@@ -34,6 +34,9 @@ enum class Command {
|
||||
ChatPinned3,
|
||||
ChatPinned4,
|
||||
ChatPinned5,
|
||||
ChatPinned6,
|
||||
ChatPinned7,
|
||||
ChatPinned8,
|
||||
|
||||
ShowAllChats,
|
||||
ShowFolder1,
|
||||
@@ -97,8 +100,10 @@ rpl::producer<not_null<Request*>> Requests();
|
||||
void Start();
|
||||
void Finish();
|
||||
|
||||
void Listen(not_null<QWidget*> widget);
|
||||
|
||||
bool Launch(Command command);
|
||||
bool HandleEvent(not_null<QShortcutEvent*> event);
|
||||
bool HandleEvent(not_null<QObject*> object, not_null<QShortcutEvent*> event);
|
||||
|
||||
const QStringList &Errors();
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ QString UiIntegration::angleBackendFilePath() {
|
||||
}
|
||||
|
||||
void UiIntegration::textActionsUpdated() {
|
||||
if (const auto window = Core::App().primaryWindow()) {
|
||||
if (const auto window = Core::App().activeWindow()) {
|
||||
window->widget()->updateGlobalMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer.h"
|
||||
#include "base/bytes.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "core/application.h"
|
||||
#include "core/changelogs.h"
|
||||
@@ -664,7 +663,7 @@ void HttpChecker::start() {
|
||||
_reply->connect(_reply, &QNetworkReply::finished, [=] {
|
||||
gotResponse();
|
||||
});
|
||||
_reply->connect(_reply, base::QNetworkReply_error, [=](auto e) {
|
||||
_reply->connect(_reply, &QNetworkReply::errorOccurred, [=](auto e) {
|
||||
gotFailure(e);
|
||||
});
|
||||
}
|
||||
@@ -703,7 +702,7 @@ void HttpChecker::clearSentRequest() {
|
||||
return;
|
||||
}
|
||||
reply->disconnect(reply, &QNetworkReply::finished, nullptr, nullptr);
|
||||
reply->disconnect(reply, base::QNetworkReply_error, nullptr, nullptr);
|
||||
reply->disconnect(reply, &QNetworkReply::errorOccurred, nullptr, nullptr);
|
||||
reply->abort();
|
||||
reply->deleteLater();
|
||||
_manager = nullptr;
|
||||
@@ -857,7 +856,7 @@ void HttpLoaderActor::sendRequest() {
|
||||
&HttpLoaderActor::partFinished);
|
||||
connect(
|
||||
_reply.get(),
|
||||
base::QNetworkReply_error,
|
||||
&QNetworkReply::errorOccurred,
|
||||
this,
|
||||
&HttpLoaderActor::partFailed);
|
||||
connect(
|
||||
@@ -1650,7 +1649,7 @@ void UpdateApplication() {
|
||||
} else {
|
||||
cSetAutoUpdate(true);
|
||||
const auto window = Core::IsAppLaunched()
|
||||
? Core::App().primaryWindow()
|
||||
? Core::App().activePrimaryWindow()
|
||||
: nullptr;
|
||||
if (window) {
|
||||
if (const auto controller = window->sessionController()) {
|
||||
|
||||
@@ -7,11 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "logs.h"
|
||||
#include "base/basic_types.h"
|
||||
#include "base/flags.h"
|
||||
#include "base/algorithm.h"
|
||||
#include "base/assertion.h"
|
||||
#include "base/bytes.h"
|
||||
|
||||
#include <crl/crl_time.h>
|
||||
@@ -177,57 +175,3 @@ inline int ceilclamp(float64 value, int32 step, int32 lowest, int32 highest) {
|
||||
lowest,
|
||||
highest);
|
||||
}
|
||||
|
||||
static int32 FullArcLength = 360 * 16;
|
||||
static int32 QuarterArcLength = (FullArcLength / 4);
|
||||
static int32 MinArcLength = (FullArcLength / 360);
|
||||
static int32 AlmostFullArcLength = (FullArcLength - MinArcLength);
|
||||
|
||||
// This pointer is used for global non-POD variables that are allocated
|
||||
// on demand by createIfNull(lambda) and are never automatically freed.
|
||||
template <typename T>
|
||||
class NeverFreedPointer {
|
||||
public:
|
||||
NeverFreedPointer() = default;
|
||||
NeverFreedPointer(const NeverFreedPointer<T> &other) = delete;
|
||||
NeverFreedPointer &operator=(const NeverFreedPointer<T> &other) = delete;
|
||||
|
||||
template <typename... Args>
|
||||
void createIfNull(Args&&... args) {
|
||||
if (isNull()) {
|
||||
reset(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
};
|
||||
|
||||
T *data() const {
|
||||
return _p;
|
||||
}
|
||||
T *release() {
|
||||
return base::take(_p);
|
||||
}
|
||||
void reset(T *p = nullptr) {
|
||||
delete _p;
|
||||
_p = p;
|
||||
}
|
||||
bool isNull() const {
|
||||
return data() == nullptr;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
reset();
|
||||
}
|
||||
T *operator->() const {
|
||||
return data();
|
||||
}
|
||||
T &operator*() const {
|
||||
Assert(!isNull());
|
||||
return *data();
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return !isNull();
|
||||
}
|
||||
|
||||
private:
|
||||
T *_p;
|
||||
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 4004003;
|
||||
constexpr auto AppVersionStr = "4.4.3";
|
||||
constexpr auto AppVersion = 4005009;
|
||||
constexpr auto AppVersionStr = "4.5.9";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -7,11 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_abstract_structure.h"
|
||||
|
||||
#include "base/never_freed_pointer.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
using DataStructures = OrderedSet<AbstractStructure**>;
|
||||
NeverFreedPointer<DataStructures> structures;
|
||||
base::NeverFreedPointer<DataStructures> structures;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -279,11 +279,11 @@ void LoadCloudFile(
|
||||
if (const auto onstack = progress) {
|
||||
onstack();
|
||||
}
|
||||
}, [=, &file](bool started) {
|
||||
}, [=, &file](FileLoader::Error error) {
|
||||
finish(file);
|
||||
file.flags |= CloudFile::Flag::Failed;
|
||||
if (const auto onstack = fail) {
|
||||
onstack(started);
|
||||
onstack(error.started);
|
||||
}
|
||||
}, [=, &file] {
|
||||
finish(file);
|
||||
|
||||
@@ -988,8 +988,9 @@ void DocumentData::handleLoaderUpdates() {
|
||||
_loader->updates(
|
||||
) | rpl::start_with_next_error_done([=] {
|
||||
_owner->documentLoadProgress(this);
|
||||
}, [=](bool started) {
|
||||
if (started && _loader) {
|
||||
}, [=](FileLoader::Error error) {
|
||||
using FailureReason = FileLoader::FailureReason;
|
||||
if (error.started && _loader) {
|
||||
const auto origin = _loader->fileOrigin();
|
||||
const auto failedFileName = _loader->fileName();
|
||||
const auto retry = [=] {
|
||||
@@ -1000,23 +1001,21 @@ void DocumentData::handleLoaderUpdates() {
|
||||
tr::lng_download_finish_failed(),
|
||||
crl::guard(&session(), retry)
|
||||
}));
|
||||
} else {
|
||||
// Sometimes we have LOCATION_INVALID error in documents / stickers.
|
||||
// Sometimes FILE_REFERENCE_EXPIRED could not be handled.
|
||||
//
|
||||
//const auto openSettings = [=] {
|
||||
// Core::App().settings().etDownloadPathBookmark(QByteArray());
|
||||
// Core::App().settings().setDownloadPath(QString());
|
||||
// Ui::show(Box<DownloadPathBox>());
|
||||
//};
|
||||
//Ui::show(Box<Ui::ConfirmBox>(
|
||||
// tr::lng_download_path_failed(tr::now),
|
||||
// tr::lng_download_path_settings(tr::now),
|
||||
// crl::guard(&session(), openSettings)));
|
||||
} else if (error.failureReason == FailureReason::FileWriteFailure) {
|
||||
if (!Core::App().settings().downloadPath().isEmpty()) {
|
||||
Core::App().settings().setDownloadPathBookmark(QByteArray());
|
||||
Core::App().settings().setDownloadPath(QString());
|
||||
Core::App().saveSettingsDelayed();
|
||||
InvokeQueued(qApp, [] {
|
||||
Ui::show(
|
||||
Ui::MakeInformBox(
|
||||
tr::lng_download_path_failed(tr::now)));
|
||||
});
|
||||
}
|
||||
}
|
||||
finishLoad();
|
||||
status = FileDownloadFailed;
|
||||
_owner->documentLoadFail(this, started);
|
||||
_owner->documentLoadFail(this, error.started);
|
||||
}, [=] {
|
||||
finishLoad();
|
||||
_owner->documentLoadDone(this);
|
||||
@@ -1198,26 +1197,29 @@ bool DocumentData::isStickerSetInstalled() const {
|
||||
|
||||
Image *DocumentData::getReplyPreview(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context) {
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler) {
|
||||
if (!hasThumbnail()) {
|
||||
return nullptr;
|
||||
} else if (!_replyPreview) {
|
||||
_replyPreview = std::make_unique<Data::ReplyPreview>(this);
|
||||
}
|
||||
return _replyPreview->image(origin, context);
|
||||
return _replyPreview->image(origin, context, spoiler);
|
||||
}
|
||||
|
||||
Image *DocumentData::getReplyPreview(not_null<HistoryItem*> item) {
|
||||
return getReplyPreview(item->fullId(), item->history()->peer);
|
||||
const auto media = item->media();
|
||||
const auto spoiler = media && media->hasSpoiler();
|
||||
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
|
||||
}
|
||||
|
||||
bool DocumentData::replyPreviewLoaded() const {
|
||||
bool DocumentData::replyPreviewLoaded(bool spoiler) const {
|
||||
if (!hasThumbnail()) {
|
||||
return true;
|
||||
} else if (!_replyPreview) {
|
||||
return false;
|
||||
}
|
||||
return _replyPreview->loaded();
|
||||
return _replyPreview->loaded(spoiler);
|
||||
}
|
||||
|
||||
StickerData *DocumentData::sticker() const {
|
||||
|
||||
@@ -142,9 +142,10 @@ public:
|
||||
|
||||
[[nodiscard]] Image *getReplyPreview(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context);
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler);
|
||||
[[nodiscard]] Image *getReplyPreview(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] bool replyPreviewLoaded() const;
|
||||
[[nodiscard]] bool replyPreviewLoaded(bool spoiler) const;
|
||||
|
||||
[[nodiscard]] StickerData *sticker() const;
|
||||
[[nodiscard]] Data::FileOrigin stickerSetOrigin() const;
|
||||
|
||||
@@ -286,10 +286,6 @@ void ResolveDocument(
|
||||
|| document->isVoiceMessage()
|
||||
|| document->isVideoMessage()) {
|
||||
::Media::Player::instance()->playPause({ document, msgId });
|
||||
} else if (item
|
||||
&& document->isAnimation()
|
||||
&& HistoryView::Gif::CanPlayInline(document)) {
|
||||
document->owner().requestAnimationPlayInline(item);
|
||||
} else {
|
||||
showDocument();
|
||||
}
|
||||
|
||||
@@ -508,9 +508,13 @@ HistoryItem *DownloadManager::lookupLoadingItem(
|
||||
void DownloadManager::loadingStopWithConfirmation(
|
||||
Fn<void()> callback,
|
||||
Main::Session *onlyInSession) {
|
||||
const auto window = Core::App().primaryWindow();
|
||||
const auto item = lookupLoadingItem(onlyInSession);
|
||||
if (!window || !item) {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
const auto window = Core::App().windowFor(
|
||||
&item->history()->session().account());
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
const auto weak = base::make_weak(&item->history()->session());
|
||||
|
||||
@@ -152,7 +152,7 @@ void DocumentOpenWithClickHandler::Open(
|
||||
data->saveFromDataSilent();
|
||||
const auto path = data->filepath(true);
|
||||
if (!path.isEmpty()) {
|
||||
File::OpenWith(path, QCursor::pos());
|
||||
File::OpenWith(path);
|
||||
} else {
|
||||
DocumentSaveClickHandler::Save(
|
||||
origin,
|
||||
|
||||
@@ -618,7 +618,7 @@ Image *MediaPhoto::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaPhoto::replyPreviewLoaded() const {
|
||||
return _photo->replyPreviewLoaded();
|
||||
return _photo->replyPreviewLoaded(_spoiler);
|
||||
}
|
||||
|
||||
TextWithEntities MediaPhoto::notificationText() const {
|
||||
@@ -854,7 +854,7 @@ Image *MediaFile::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaFile::replyPreviewLoaded() const {
|
||||
return _document->replyPreviewLoaded();
|
||||
return _document->replyPreviewLoaded(_spoiler);
|
||||
}
|
||||
|
||||
ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
|
||||
@@ -1323,7 +1323,7 @@ TextForMimeData MediaLocation::clipboardText() const {
|
||||
if (!descriptionResult.text.isEmpty()) {
|
||||
result.append(std::move(descriptionResult));
|
||||
}
|
||||
result.append(LocationClickHandler(_point).dragText());
|
||||
result.append(LocationClickHandler(_point).url());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1479,10 +1479,11 @@ Image *MediaWebPage::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaWebPage::replyPreviewLoaded() const {
|
||||
const auto spoiler = false;
|
||||
if (const auto document = MediaWebPage::document()) {
|
||||
return document->replyPreviewLoaded();
|
||||
return document->replyPreviewLoaded(spoiler);
|
||||
} else if (const auto photo = MediaWebPage::photo()) {
|
||||
return photo->replyPreviewLoaded();
|
||||
return photo->replyPreviewLoaded(spoiler);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1552,10 +1553,11 @@ Image *MediaGame::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaGame::replyPreviewLoaded() const {
|
||||
const auto spoiler = false;
|
||||
if (const auto document = _game->document) {
|
||||
return document->replyPreviewLoaded();
|
||||
return document->replyPreviewLoaded(spoiler);
|
||||
} else if (const auto photo = _game->photo) {
|
||||
return photo->replyPreviewLoaded();
|
||||
return photo->replyPreviewLoaded(spoiler);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1675,8 +1677,9 @@ Image *MediaInvoice::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaInvoice::replyPreviewLoaded() const {
|
||||
const auto spoiler = false;
|
||||
if (const auto photo = _invoice.photo) {
|
||||
return photo->replyPreviewLoaded();
|
||||
return photo->replyPreviewLoaded(spoiler);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -514,7 +514,8 @@ bool ChannelHasActiveCall(not_null<ChannelData*> channel) {
|
||||
|
||||
rpl::producer<QImage> PeerUserpicImageValue(
|
||||
not_null<PeerData*> peer,
|
||||
int size) {
|
||||
int size,
|
||||
std::optional<int> radius) {
|
||||
return [=](auto consumer) {
|
||||
auto result = rpl::lifetime();
|
||||
struct State {
|
||||
@@ -541,7 +542,10 @@ rpl::producer<QImage> PeerUserpicImageValue(
|
||||
}
|
||||
state->key = key;
|
||||
state->empty = false;
|
||||
consumer.put_next(peer->generateUserpicImage(state->view, size));
|
||||
consumer.put_next(peer->generateUserpicImage(
|
||||
state->view,
|
||||
size,
|
||||
radius));
|
||||
};
|
||||
peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
|
||||
@@ -133,7 +133,8 @@ inline auto PeerFullFlagValue(
|
||||
|
||||
[[nodiscard]] rpl::producer<QImage> PeerUserpicImageValue(
|
||||
not_null<PeerData*> peer,
|
||||
int size);
|
||||
int size,
|
||||
std::optional<int> radius = {});
|
||||
|
||||
[[nodiscard]] const AllowedReactions &PeerAllowedReactions(
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kPhotoSideLimit = 1280;
|
||||
constexpr auto kPhotoSideLimit = 2560;
|
||||
|
||||
using Data::PhotoMedia;
|
||||
using Data::PhotoSize;
|
||||
@@ -209,22 +209,25 @@ bool PhotoData::uploading() const {
|
||||
|
||||
Image *PhotoData::getReplyPreview(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context) {
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler) {
|
||||
if (!_replyPreview) {
|
||||
_replyPreview = std::make_unique<Data::ReplyPreview>(this);
|
||||
}
|
||||
return _replyPreview->image(origin, context);
|
||||
return _replyPreview->image(origin, context, spoiler);
|
||||
}
|
||||
|
||||
Image *PhotoData::getReplyPreview(not_null<HistoryItem*> item) {
|
||||
return getReplyPreview(item->fullId(), item->history()->peer);
|
||||
const auto media = item->media();
|
||||
const auto spoiler = media && media->hasSpoiler();
|
||||
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
|
||||
}
|
||||
|
||||
bool PhotoData::replyPreviewLoaded() const {
|
||||
bool PhotoData::replyPreviewLoaded(bool spoiler) const {
|
||||
if (!_replyPreview) {
|
||||
return false;
|
||||
}
|
||||
return _replyPreview->loaded();
|
||||
return _replyPreview->loaded(spoiler);
|
||||
}
|
||||
|
||||
void PhotoData::setRemoteLocation(
|
||||
|
||||
@@ -66,9 +66,10 @@ public:
|
||||
|
||||
[[nodiscard]] Image *getReplyPreview(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context);
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler);
|
||||
[[nodiscard]] Image *getReplyPreview(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] bool replyPreviewLoaded() const;
|
||||
[[nodiscard]] bool replyPreviewLoaded(bool spoiler) const;
|
||||
|
||||
void setRemoteLocation(
|
||||
int32 dc,
|
||||
|
||||
@@ -18,6 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/file_download.h"
|
||||
#include "ui/image/image.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
namespace Data {
|
||||
|
||||
PhotoMedia::PhotoMedia(not_null<PhotoData*> owner)
|
||||
@@ -101,6 +104,14 @@ void PhotoMedia::set(
|
||||
QImage image,
|
||||
QByteArray bytes) {
|
||||
const auto index = PhotoSizeIndex(size);
|
||||
const auto limit = PhotoData::SideLimit();
|
||||
if (image.width() > limit || image.height() > limit) {
|
||||
image = image.scaled(
|
||||
limit,
|
||||
limit,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
_images[index] = PhotoImage{
|
||||
.data = std::make_unique<Image>(std::move(image)),
|
||||
.bytes = std::move(bytes),
|
||||
@@ -190,8 +201,7 @@ bool PhotoMedia::saveToFile(const QString &path) {
|
||||
QFile f(path);
|
||||
return f.open(QIODevice::WriteOnly)
|
||||
&& (f.write(video) == video.size());
|
||||
} else if (const auto photo = imageBytes(large)
|
||||
; !photo.isEmpty()) {
|
||||
} else if (const auto photo = imageBytes(large); !photo.isEmpty()) {
|
||||
QFile f(path);
|
||||
return f.open(QIODevice::WriteOnly)
|
||||
&& (f.write(photo) == photo.size());
|
||||
@@ -202,4 +212,24 @@ bool PhotoMedia::saveToFile(const QString &path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PhotoMedia::setToClipboard() {
|
||||
constexpr auto large = PhotoSize::Large;
|
||||
if (const auto video = videoContent(large); !video.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
auto fallback = image(large)->original();
|
||||
if (fallback.isNull()) {
|
||||
return false;
|
||||
}
|
||||
const auto bytes = imageBytes(large);
|
||||
auto mime = std::make_unique<QMimeData>();
|
||||
mime->setImageData(std::move(fallback));
|
||||
if (auto bytes = imageBytes(large); !bytes.isEmpty()) {
|
||||
mime->setData(u"image/jpeg"_q, std::move(bytes));
|
||||
}
|
||||
mime->setData(u"application/x-td-use-jpeg"_q, "1");
|
||||
QGuiApplication::clipboard()->setMimeData(mime.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -48,6 +48,7 @@ public:
|
||||
void collectLocalData(not_null<PhotoMedia*> local);
|
||||
|
||||
bool saveToFile(const QString &path);
|
||||
bool setToClipboard();
|
||||
|
||||
private:
|
||||
struct PhotoImage {
|
||||
|
||||
@@ -27,7 +27,11 @@ ReplyPreview::ReplyPreview(not_null<PhotoData*> photo)
|
||||
|
||||
ReplyPreview::~ReplyPreview() = default;
|
||||
|
||||
void ReplyPreview::prepare(not_null<Image*> image, Images::Options options) {
|
||||
void ReplyPreview::prepare(
|
||||
not_null<Image*> image,
|
||||
Images::Options options,
|
||||
bool spoiler) {
|
||||
using namespace Images;
|
||||
if (image->isNull()) {
|
||||
return;
|
||||
}
|
||||
@@ -41,24 +45,34 @@ void ReplyPreview::prepare(not_null<Image*> image, Images::Options options) {
|
||||
: QSize(
|
||||
st::msgReplyBarSize.height(),
|
||||
h * st::msgReplyBarSize.height() / w);
|
||||
thumbSize *= cIntRetinaFactor();
|
||||
options |= Images::Option::TransparentBackground;
|
||||
thumbSize *= style::DevicePixelRatio();
|
||||
options |= Option::TransparentBackground;
|
||||
auto outerSize = st::msgReplyBarSize.height();
|
||||
auto bitmap = image->pixNoCache(
|
||||
thumbSize,
|
||||
{ .options = options, .outer = { outerSize, outerSize } });
|
||||
_image = std::make_unique<Image>(bitmap.toImage());
|
||||
_good = ((options & Images::Option::Blur) == 0);
|
||||
auto original = spoiler
|
||||
? image->original().scaled(
|
||||
{ 40, 40 },
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation)
|
||||
: image->original();
|
||||
auto prepared = Prepare(std::move(original), thumbSize, {
|
||||
.options = options | (spoiler ? Option::Blur : Option()),
|
||||
.outer = { outerSize, outerSize },
|
||||
});
|
||||
(spoiler ? _spoilered : _regular) = std::make_unique<Image>(
|
||||
std::move(prepared));
|
||||
_good = spoiler || ((options & Option::Blur) == 0);
|
||||
}
|
||||
|
||||
Image *ReplyPreview::image(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context) {
|
||||
if (_checked) {
|
||||
return _image.get();
|
||||
}
|
||||
if (_document) {
|
||||
if (!_image || (!_good && _document->hasThumbnail())) {
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler) {
|
||||
auto &image = spoiler ? _spoilered : _regular;
|
||||
auto &checked = spoiler ? _checkedSpoilered : _checkedRegular;
|
||||
if (checked) {
|
||||
return image.get();
|
||||
} else if (_document) {
|
||||
if (!image || (!_good && _document->hasThumbnail())) {
|
||||
if (!_documentMedia) {
|
||||
_documentMedia = _document->createMediaView();
|
||||
_documentMedia->thumbnailWanted(origin);
|
||||
@@ -67,51 +81,66 @@ Image *ReplyPreview::image(
|
||||
const auto option = _document->isVideoMessage()
|
||||
? Images::Option::RoundCircle
|
||||
: Images::Option::None;
|
||||
if (thumbnail) {
|
||||
if (spoiler) {
|
||||
if (const auto image = _documentMedia->thumbnailInline()) {
|
||||
prepare(image, option, true);
|
||||
} else if (thumbnail) {
|
||||
prepare(thumbnail, option, true);
|
||||
}
|
||||
} else if (thumbnail) {
|
||||
prepare(thumbnail, option);
|
||||
} else if (!_image) {
|
||||
} else if (!image) {
|
||||
if (const auto image = _documentMedia->thumbnailInline()) {
|
||||
prepare(image, option | Images::Option::Blur);
|
||||
}
|
||||
}
|
||||
if (_good || !_document->hasThumbnail()) {
|
||||
_checked = true;
|
||||
checked = true;
|
||||
_documentMedia = nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Assert(_photo != nullptr);
|
||||
if (!_image || !_good) {
|
||||
if (!image || !_good) {
|
||||
const auto inlineThumbnailBytes = _photo->inlineThumbnailBytes();
|
||||
if (!_photoMedia) {
|
||||
_photoMedia = _photo->createMediaView();
|
||||
}
|
||||
using Size = PhotoSize;
|
||||
const auto loadThumbnail = inlineThumbnailBytes.isEmpty()
|
||||
|| _photoMedia->autoLoadThumbnailAllowed(context);
|
||||
|| (!spoiler
|
||||
&& _photoMedia->autoLoadThumbnailAllowed(context));
|
||||
if (loadThumbnail) {
|
||||
_photoMedia->wanted(PhotoSize::Small, origin);
|
||||
_photoMedia->wanted(Size::Small, origin);
|
||||
}
|
||||
if (const auto small = _photoMedia->image(PhotoSize::Small)) {
|
||||
prepare(small, Images::Option(0));
|
||||
} else if (const auto large = _photoMedia->image(
|
||||
PhotoSize::Large)) {
|
||||
prepare(large, Images::Option(0));
|
||||
} else if (!_image) {
|
||||
if (spoiler) {
|
||||
if (const auto blurred = _photoMedia->thumbnailInline()) {
|
||||
prepare(blurred, {}, true);
|
||||
} else if (const auto small = _photoMedia->image(Size::Small)) {
|
||||
prepare(small, {}, true);
|
||||
} else if (const auto large = _photoMedia->image(Size::Large)) {
|
||||
prepare(large, {}, true);
|
||||
}
|
||||
} else if (const auto small = _photoMedia->image(Size::Small)) {
|
||||
prepare(small, {});
|
||||
} else if (const auto large = _photoMedia->image(Size::Large)) {
|
||||
prepare(large, {});
|
||||
} else if (!image) {
|
||||
if (const auto blurred = _photoMedia->thumbnailInline()) {
|
||||
prepare(blurred, Images::Option::Blur);
|
||||
}
|
||||
}
|
||||
if (_good) {
|
||||
_checked = true;
|
||||
checked = true;
|
||||
_photoMedia = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return _image.get();
|
||||
return image.get();
|
||||
}
|
||||
|
||||
bool ReplyPreview::loaded() const {
|
||||
return _checked;
|
||||
bool ReplyPreview::loaded(bool spoiler) const {
|
||||
return spoiler ? _checkedSpoilered : _checkedRegular;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -25,19 +25,25 @@ public:
|
||||
|
||||
[[nodiscard]] Image *image(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context);
|
||||
[[nodiscard]] bool loaded() const;
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler);
|
||||
[[nodiscard]] bool loaded(bool spoiler) const;
|
||||
|
||||
private:
|
||||
void prepare(not_null<Image*> image, Images::Options options);
|
||||
void prepare(
|
||||
not_null<Image*> image,
|
||||
Images::Options options,
|
||||
bool spoiler = false);
|
||||
|
||||
std::unique_ptr<Image> _image;
|
||||
std::unique_ptr<Image> _regular;
|
||||
std::unique_ptr<Image> _spoilered;
|
||||
PhotoData *_photo = nullptr;
|
||||
DocumentData *_document = nullptr;
|
||||
std::shared_ptr<PhotoMedia> _photoMedia;
|
||||
std::shared_ptr<DocumentMedia> _documentMedia;
|
||||
bool _good = false;
|
||||
bool _checked = false;
|
||||
bool _checkedRegular = false;
|
||||
bool _checkedSpoilered = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -805,6 +805,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||
const auto canViewMembers = channel->canViewMembers();
|
||||
const auto canAddMembers = channel->canAddMembers();
|
||||
|
||||
const auto wasCallNotEmpty = Data::ChannelHasActiveCall(channel);
|
||||
|
||||
if (const auto count = data.vparticipants_count()) {
|
||||
channel->setMembersCount(count->v);
|
||||
}
|
||||
@@ -912,6 +914,9 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||
|| canAddMembers != channel->canAddMembers()) {
|
||||
flags |= UpdateFlag::Rights;
|
||||
}
|
||||
if (wasCallNotEmpty != Data::ChannelHasActiveCall(channel)) {
|
||||
flags |= UpdateFlag::GroupCall;
|
||||
}
|
||||
}, [&](const MTPDchannelForbidden &data) {
|
||||
const auto channel = result->asChannel();
|
||||
|
||||
@@ -1695,29 +1700,12 @@ void Session::requestItemTextRefresh(not_null<HistoryItem*> item) {
|
||||
}
|
||||
}
|
||||
|
||||
void Session::requestAnimationPlayInline(not_null<HistoryItem*> item) {
|
||||
_animationPlayInlineRequest.fire_copy(item);
|
||||
|
||||
if (const auto media = item->media()) {
|
||||
if (const auto data = media->document()) {
|
||||
if (data && data->isVideoMessage()) {
|
||||
const auto msgId = item->fullId();
|
||||
::Media::Player::instance()->playPause({ data, msgId });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Session::requestUnreadReactionsAnimation(not_null<HistoryItem*> item) {
|
||||
enumerateItemViews(item, [&](not_null<ViewElement*> view) {
|
||||
view->animateUnreadReactions();
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<not_null<HistoryItem*>> Session::animationPlayInlineRequest() const {
|
||||
return _animationPlayInlineRequest.events();
|
||||
}
|
||||
|
||||
rpl::producer<not_null<const HistoryItem*>> Session::itemRemoved() const {
|
||||
return _itemRemoved.events();
|
||||
}
|
||||
|
||||
@@ -275,9 +275,7 @@ public:
|
||||
void requestItemViewRefresh(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> itemViewRefreshRequest() const;
|
||||
void requestItemTextRefresh(not_null<HistoryItem*> item);
|
||||
void requestAnimationPlayInline(not_null<HistoryItem*> item);
|
||||
void requestUnreadReactionsAnimation(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> animationPlayInlineRequest() const;
|
||||
void notifyHistoryUnloaded(not_null<const History*> history);
|
||||
[[nodiscard]] rpl::producer<not_null<const History*>> historyUnloaded() const;
|
||||
void notifyItemDataChange(not_null<HistoryItem*> item);
|
||||
@@ -861,7 +859,6 @@ private:
|
||||
rpl::event_stream<not_null<HistoryItem*>> _itemViewRefreshRequest;
|
||||
rpl::event_stream<not_null<HistoryItem*>> _itemTextRefreshRequest;
|
||||
rpl::event_stream<not_null<HistoryItem*>> _itemDataChanges;
|
||||
rpl::event_stream<not_null<HistoryItem*>> _animationPlayInlineRequest;
|
||||
rpl::event_stream<not_null<const HistoryItem*>> _itemRemoved;
|
||||
rpl::event_stream<not_null<const ViewElement*>> _viewRemoved;
|
||||
rpl::event_stream<not_null<const History*>> _historyUnloaded;
|
||||
|
||||
@@ -144,6 +144,8 @@ WebPageType ParseWebPageType(
|
||||
return WebPageType::Video;
|
||||
} else if (type == u"photo"_q) {
|
||||
return WebPageType::Photo;
|
||||
} else if (type == u"document"_q) {
|
||||
return WebPageType::Document;
|
||||
} else if (type == u"profile"_q) {
|
||||
return WebPageType::Profile;
|
||||
} else if (type == u"telegram_background"_q) {
|
||||
|
||||
@@ -26,6 +26,7 @@ enum class WebPageType {
|
||||
|
||||
Photo,
|
||||
Video,
|
||||
Document,
|
||||
|
||||
User,
|
||||
Bot,
|
||||
|
||||
@@ -926,7 +926,8 @@ void Stickers::specialSetReceived(
|
||||
LOG(("API Error: "
|
||||
"received recent attached stickers hash %1 "
|
||||
"while counted hash is %2"
|
||||
).arg(hash, counted));
|
||||
).arg(hash
|
||||
).arg(counted));
|
||||
}
|
||||
session().local().writeRecentMasks();
|
||||
} break;
|
||||
|
||||
@@ -475,8 +475,8 @@ editTopicMaxHeight: 408px;
|
||||
|
||||
chooseTopicListItem: PeerListItem(defaultPeerListItem) {
|
||||
height: 44px;
|
||||
photoSize: 28px;
|
||||
photoPosition: point(8px, 5px);
|
||||
photoSize: 20px;
|
||||
photoPosition: point(16px, 12px);
|
||||
namePosition: point(55px, 11px);
|
||||
nameStyle: TextStyle(defaultTextStyle) {
|
||||
font: font(14px semibold);
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace {
|
||||
constexpr auto kHashtagResultsLimit = 5;
|
||||
constexpr auto kStartReorderThreshold = 30;
|
||||
|
||||
base::options::toggle TabbedPanelShowOnClick({
|
||||
base::options::toggle CtrlClickChatNewWindow({
|
||||
.id = kOptionCtrlClickChatNewWindow,
|
||||
.name = "New chat window by Ctrl+Click",
|
||||
.description = "Open chat in a new window by Ctrl+Click "
|
||||
@@ -2851,7 +2851,6 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
|
||||
_searchInChat = key;
|
||||
_searchFromPeer = from;
|
||||
if (_searchInChat) {
|
||||
_controller->closeFolder();
|
||||
onHashtagFilterUpdate(QStringView());
|
||||
_cancelSearchInChat->show();
|
||||
} else {
|
||||
@@ -3311,7 +3310,7 @@ bool InnerWidget::chooseRow(
|
||||
const auto modifyChosenRow = [](
|
||||
ChosenRow row,
|
||||
Qt::KeyboardModifiers modifiers) {
|
||||
if (TabbedPanelShowOnClick.value()) {
|
||||
if (CtrlClickChatNewWindow.value()) {
|
||||
row.newWindow = (modifiers & Qt::ControlModifier);
|
||||
}
|
||||
return row;
|
||||
@@ -3739,6 +3738,9 @@ void InnerWidget::setupShortcuts() {
|
||||
Command::ChatPinned3,
|
||||
Command::ChatPinned4,
|
||||
Command::ChatPinned5,
|
||||
Command::ChatPinned6,
|
||||
Command::ChatPinned7,
|
||||
Command::ChatPinned8,
|
||||
};
|
||||
auto &&pinned = ranges::views::zip(
|
||||
kPinned,
|
||||
@@ -3791,14 +3793,14 @@ void InnerWidget::setupShortcuts() {
|
||||
});
|
||||
|
||||
request->check(Command::ReadChat) && request->handle([=] {
|
||||
const auto history = _selected ? _selected->history() : nullptr;
|
||||
if (history) {
|
||||
if (history->chatListBadgesState().unread) {
|
||||
session().data().histories().readInbox(history);
|
||||
}
|
||||
return true;
|
||||
const auto thread = _selected ? _selected->thread() : nullptr;
|
||||
if (!thread) {
|
||||
return false;
|
||||
}
|
||||
return (history != nullptr);
|
||||
if (Window::IsUnreadThread(thread)) {
|
||||
Window::MarkAsReadThread(thread);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
request->check(Command::ShowContacts) && request->handle([=] {
|
||||
|
||||
@@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "data/data_session.h"
|
||||
#include "mainwidget.h"
|
||||
|
||||
namespace Dialogs {
|
||||
|
||||
|
||||
@@ -91,19 +91,19 @@ constexpr auto kNoneLayer = 0;
|
||||
style::al_center);
|
||||
|
||||
constexpr auto kPenWidth = 1.5;
|
||||
constexpr auto kAngleStart = 90 * 16;
|
||||
constexpr auto kAngleSpan = 180 * 16;
|
||||
|
||||
const auto penWidth = style::ConvertScaleExact(kPenWidth);
|
||||
auto pen = QPen(st::premiumButtonFg);
|
||||
pen.setJoinStyle(Qt::RoundJoin);
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setWidthF(style::ConvertScaleExact(kPenWidth));
|
||||
pen.setWidthF(penWidth);
|
||||
|
||||
q.setPen(pen);
|
||||
q.setBrush(Qt::NoBrush);
|
||||
q.drawArc(innerRect, kAngleStart, kAngleSpan);
|
||||
q.drawArc(innerRect, arc::kQuarterLength, arc::kHalfLength);
|
||||
|
||||
q.setClipRect(innerRect - QMargins(innerRect.width() / 2, 0, 0, 0));
|
||||
q.setClipRect(innerRect
|
||||
- QMargins(innerRect.width() / 2, 0, -penWidth, -penWidth));
|
||||
pen.setStyle(Qt::DotLine);
|
||||
q.setPen(pen);
|
||||
q.drawEllipse(innerRect);
|
||||
@@ -252,6 +252,10 @@ Row::Row(Key key, int index, int top) : _id(key), _top(top), _index(index) {
|
||||
}
|
||||
}
|
||||
|
||||
Row::~Row() {
|
||||
clearTopicJumpRipple();
|
||||
}
|
||||
|
||||
void Row::recountHeight(float64 narrowRatio) {
|
||||
if (const auto history = _id.history()) {
|
||||
_height = history->isForum()
|
||||
@@ -487,6 +491,11 @@ void Row::stopLastRipple() {
|
||||
}
|
||||
}
|
||||
|
||||
void Row::clearRipple() {
|
||||
BasicRow::clearRipple();
|
||||
clearTopicJumpRipple();
|
||||
}
|
||||
|
||||
void Row::addTopicJumpRipple(
|
||||
QPoint origin,
|
||||
not_null<Ui::TopicJumpCache*> topicJumpCache,
|
||||
@@ -503,10 +512,15 @@ void Row::addTopicJumpRipple(
|
||||
}
|
||||
|
||||
void Row::clearTopicJumpRipple() {
|
||||
if (_topicJumpRipple) {
|
||||
clearRipple();
|
||||
_topicJumpRipple = 0;
|
||||
if (!_topicJumpRipple) {
|
||||
return;
|
||||
}
|
||||
const auto history = this->history();
|
||||
const auto view = history ? &history->lastItemDialogsView() : nullptr;
|
||||
if (view) {
|
||||
view->clearRipple();
|
||||
}
|
||||
_topicJumpRipple = 0;
|
||||
}
|
||||
|
||||
bool Row::topicJumpRipple() const {
|
||||
|
||||
@@ -53,11 +53,11 @@ public:
|
||||
|
||||
void addRipple(QPoint origin, QSize size, Fn<void()> updateCallback);
|
||||
virtual void stopLastRipple();
|
||||
virtual void clearRipple();
|
||||
void addRippleWithMask(
|
||||
QPoint origin,
|
||||
QImage mask,
|
||||
Fn<void()> updateCallback);
|
||||
void clearRipple();
|
||||
|
||||
void paintRipple(
|
||||
QPainter &p,
|
||||
@@ -82,6 +82,7 @@ public:
|
||||
explicit Row(std::nullptr_t) {
|
||||
}
|
||||
Row(Key key, int index, int top);
|
||||
~Row();
|
||||
|
||||
[[nodiscard]] int top() const {
|
||||
return _top;
|
||||
@@ -105,6 +106,7 @@ public:
|
||||
|
||||
[[nodiscard]] bool lookupIsInTopicJump(int x, int y) const;
|
||||
void stopLastRipple() override;
|
||||
void clearRipple() override;
|
||||
void addTopicJumpRipple(
|
||||
QPoint origin,
|
||||
not_null<Ui::TopicJumpCache*> topicJumpCache,
|
||||
|
||||
@@ -188,7 +188,12 @@ Widget::Widget(
|
||||
+ st::defaultDialogRow.photoSize
|
||||
+ st::defaultDialogRow.padding.left())
|
||||
, _searchControls(this)
|
||||
, _mainMenuToggle(_searchControls, st::dialogsMenuToggle)
|
||||
, _mainMenu({
|
||||
.toggle = object_ptr<Ui::IconButton>(
|
||||
_searchControls,
|
||||
st::dialogsMenuToggle),
|
||||
.under = object_ptr<Ui::AbstractButton>(_searchControls),
|
||||
})
|
||||
, _searchForNarrowFilters(_searchControls, st::dialogsSearchForNarrowFilters)
|
||||
, _filter(_searchControls, st::dialogsFilter, tr::lng_dlg_filter())
|
||||
, _chooseFromUser(
|
||||
@@ -427,6 +432,14 @@ Widget::Widget(
|
||||
updateControlsGeometry();
|
||||
}, lifetime());
|
||||
|
||||
_childListShown.changes(
|
||||
) | rpl::filter((rpl::mappers::_1 == 0.) || (rpl::mappers::_1 == 1.)
|
||||
) | rpl::start_with_next([=](float64 shown) {
|
||||
const auto color = (shown > 0.) ? &st::dialogsRippleBg : nullptr;
|
||||
_mainMenu.toggle->setRippleColorOverride(color);
|
||||
_searchForNarrowFilters->setRippleColorOverride(color);
|
||||
}, lifetime());
|
||||
|
||||
setupDownloadBar();
|
||||
}
|
||||
}
|
||||
@@ -662,13 +675,18 @@ void Widget::setupSupportMode() {
|
||||
}
|
||||
|
||||
void Widget::setupMainMenuToggle() {
|
||||
_mainMenuToggle->setClickedCallback([=] { showMainMenu(); });
|
||||
_mainMenu.under->setClickedCallback([=] {
|
||||
_mainMenu.toggle->clicked({}, Qt::LeftButton);
|
||||
});
|
||||
_mainMenu.under->stackUnder(_mainMenu.toggle);
|
||||
_mainMenu.toggle->setClickedCallback([=] { showMainMenu(); });
|
||||
|
||||
rpl::single(rpl::empty) | rpl::then(
|
||||
controller()->filtersMenuChanged()
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto filtersHidden = !controller()->filtersWidth();
|
||||
_mainMenuToggle->setVisible(filtersHidden);
|
||||
_mainMenu.toggle->setVisible(filtersHidden);
|
||||
_mainMenu.under->setVisible(filtersHidden);
|
||||
_searchForNarrowFilters->setVisible(!filtersHidden);
|
||||
updateControlsGeometry();
|
||||
}, lifetime());
|
||||
@@ -680,8 +698,8 @@ void Widget::setupMainMenuToggle() {
|
||||
: !state.allMuted
|
||||
? &st::dialogsMenuToggleUnread
|
||||
: &st::dialogsMenuToggleUnreadMuted;
|
||||
_mainMenuToggle->setIconOverride(icon, icon);
|
||||
}, _mainMenuToggle->lifetime());
|
||||
_mainMenu.toggle->setIconOverride(icon, icon);
|
||||
}, _mainMenu.toggle->lifetime());
|
||||
}
|
||||
|
||||
void Widget::setupShortcuts() {
|
||||
@@ -1463,9 +1481,7 @@ void Widget::showMainMenu() {
|
||||
controller()->widget()->showMainMenu();
|
||||
}
|
||||
|
||||
void Widget::searchMessages(
|
||||
const QString &query,
|
||||
Key inChat) {
|
||||
void Widget::searchMessages(const QString &query, Key inChat) {
|
||||
if (_childList) {
|
||||
const auto forum = controller()->shownForum().current();
|
||||
const auto topic = inChat.topic();
|
||||
@@ -1476,18 +1492,27 @@ void Widget::searchMessages(
|
||||
}
|
||||
hideChildList();
|
||||
}
|
||||
if (_openedFolder) {
|
||||
controller()->closeFolder();
|
||||
}
|
||||
|
||||
const auto inChatChanged = [&] {
|
||||
const auto inPeer = inChat.peer();
|
||||
const auto inTopic = inChat.topic();
|
||||
if (!inTopic && _openedForum && inPeer == _openedForum->channel()) {
|
||||
if (!inTopic
|
||||
&& _openedForum
|
||||
&& inPeer == _openedForum->channel()
|
||||
&& _subsectionTopBar
|
||||
&& _subsectionTopBar->searchMode()) {
|
||||
return false;
|
||||
} else if ((inTopic || (inPeer && !inPeer->isForum()))
|
||||
&& (inChat == _searchInChat)) {
|
||||
return false;
|
||||
} else if (const auto inPeer = inChat.peer()) {
|
||||
if (inPeer->migrateTo() == _searchInChat.peer()
|
||||
&& !_searchInChat.topic()) {
|
||||
return false;
|
||||
if (const auto to = inPeer->migrateTo()) {
|
||||
if (to == _searchInChat.peer() && !_searchInChat.topic()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -2112,18 +2137,7 @@ void Widget::closeChildList(anim::type animated) {
|
||||
}
|
||||
|
||||
void Widget::searchInChat(Key chat) {
|
||||
if (_openedForum && !chat.peer()->forum()) {
|
||||
controller()->closeForum();
|
||||
}
|
||||
if (_openedFolder) {
|
||||
if (_childList && _childList->setSearchInChat(chat)) {
|
||||
return;
|
||||
}
|
||||
controller()->closeFolder();
|
||||
}
|
||||
cancelSearch();
|
||||
setSearchInChat(chat);
|
||||
applyFilterUpdate(true);
|
||||
searchMessages(QString(), chat);
|
||||
}
|
||||
|
||||
bool Widget::setSearchInChat(Key chat, PeerData *from) {
|
||||
@@ -2160,15 +2174,13 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
|
||||
}
|
||||
}
|
||||
_searchInMigrated = nullptr;
|
||||
if (peer) {
|
||||
if (peer && !forum) {
|
||||
if (_layout != Layout::Main) {
|
||||
return false;
|
||||
} else if (const auto migrateTo = peer->migrateTo()) {
|
||||
return setSearchInChat(peer->owner().history(migrateTo), from);
|
||||
} else if (const auto migrateFrom = peer->migrateFrom()) {
|
||||
if (!forum) {
|
||||
_searchInMigrated = peer->owner().history(migrateFrom);
|
||||
}
|
||||
_searchInMigrated = peer->owner().history(migrateFrom);
|
||||
}
|
||||
}
|
||||
if (searchInPeerUpdated) {
|
||||
@@ -2180,6 +2192,9 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
|
||||
updateSearchFromVisibility();
|
||||
clearSearchCache();
|
||||
}
|
||||
if (_searchInChat && _layout == Layout::Main) {
|
||||
controller()->closeFolder();
|
||||
}
|
||||
_inner->searchInChat(_searchInChat, _searchFromAuthor);
|
||||
if (_subsectionTopBar) {
|
||||
_subsectionTopBar->searchEnableJumpToDate(
|
||||
@@ -2368,7 +2383,7 @@ void Widget::updateControlsGeometry() {
|
||||
|
||||
auto filterLeft = (controller()->filtersWidth()
|
||||
? st::dialogsFilterSkip
|
||||
: (st::dialogsFilterPadding.x() + _mainMenuToggle->width()))
|
||||
: (st::dialogsFilterPadding.x() + _mainMenu.toggle->width()))
|
||||
+ st::dialogsFilterPadding.x();
|
||||
auto filterRight = (session().domain().local().hasLocalPasscode()
|
||||
? (st::dialogsFilterPadding.x() + _lockUnlock->width())
|
||||
@@ -2388,9 +2403,16 @@ void Widget::updateControlsGeometry() {
|
||||
_filter->setGeometryToLeft(filterLeft, filterTop, filterWidth, _filter->height());
|
||||
auto mainMenuLeft = anim::interpolate(
|
||||
st::dialogsFilterPadding.x(),
|
||||
(_narrowWidth - _mainMenuToggle->width()) / 2,
|
||||
(_narrowWidth - _mainMenu.toggle->width()) / 2,
|
||||
narrowRatio);
|
||||
_mainMenuToggle->moveToLeft(mainMenuLeft, st::dialogsFilterPadding.y());
|
||||
_mainMenu.toggle->moveToLeft(mainMenuLeft, st::dialogsFilterPadding.y());
|
||||
_mainMenu.under->setGeometry(
|
||||
0,
|
||||
0,
|
||||
filterLeft,
|
||||
_mainMenu.toggle->y()
|
||||
+ _mainMenu.toggle->height()
|
||||
+ st::dialogsFilterPadding.y());
|
||||
const auto searchLeft = anim::interpolate(
|
||||
-_searchForNarrowFilters->width(),
|
||||
(_narrowWidth - _searchForNarrowFilters->width()) / 2,
|
||||
|
||||
@@ -33,6 +33,7 @@ class ContactStatus;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace Ui {
|
||||
class AbstractButton;
|
||||
class IconButton;
|
||||
class PopupMenu;
|
||||
class DropdownMenu;
|
||||
@@ -222,7 +223,10 @@ private:
|
||||
int _narrowWidth = 0;
|
||||
object_ptr<Ui::RpWidget> _searchControls;
|
||||
object_ptr<HistoryView::TopBarWidget> _subsectionTopBar = { nullptr } ;
|
||||
object_ptr<Ui::IconButton> _mainMenuToggle;
|
||||
struct {
|
||||
object_ptr<Ui::IconButton> toggle;
|
||||
object_ptr<Ui::AbstractButton> under;
|
||||
} _mainMenu;
|
||||
object_ptr<Ui::IconButton> _searchForNarrowFilters;
|
||||
object_ptr<Ui::InputField> _filter;
|
||||
object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _chooseFromUser;
|
||||
|
||||
@@ -227,6 +227,12 @@ void MessageView::stopLastRipple() {
|
||||
}
|
||||
}
|
||||
|
||||
void MessageView::clearRipple() {
|
||||
if (_topics) {
|
||||
_topics->clearRipple();
|
||||
}
|
||||
}
|
||||
|
||||
int MessageView::countWidth() const {
|
||||
auto result = 0;
|
||||
if (!_senderCache.isEmpty()) {
|
||||
|
||||
@@ -73,6 +73,7 @@ public:
|
||||
not_null<TopicJumpCache*> topicJumpCache,
|
||||
Fn<void()> updateCallback);
|
||||
void stopLastRipple();
|
||||
void clearRipple();
|
||||
|
||||
private:
|
||||
struct LoadingContext;
|
||||
|
||||
@@ -187,6 +187,10 @@ void TopicsView::stopLastRipple() {
|
||||
}
|
||||
}
|
||||
|
||||
void TopicsView::clearRipple() {
|
||||
_ripple = nullptr;
|
||||
}
|
||||
|
||||
void TopicsView::paintRipple(
|
||||
QPainter &p,
|
||||
int x,
|
||||
|
||||
@@ -89,8 +89,8 @@ public:
|
||||
int y,
|
||||
int outerWidth,
|
||||
const QColor *colorOverride) const;
|
||||
void clearRipple();
|
||||
void stopLastRipple();
|
||||
void clearRipple();
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
|
||||
@@ -54,8 +54,8 @@ photoEditorRotateButton: IconButton(defaultIconButton) {
|
||||
width: photoEditorButtonBarHeight;
|
||||
height: photoEditorButtonBarHeight;
|
||||
|
||||
icon: icon {{ "photo_editor/rotate", photoEditorButtonIconFg }};
|
||||
iconOver: icon {{ "photo_editor/rotate", photoEditorButtonIconFgOver }};
|
||||
icon: icon {{ "photo_editor/rotate-flip_horizontal", photoEditorButtonIconFg }};
|
||||
iconOver: icon {{ "photo_editor/rotate-flip_horizontal", photoEditorButtonIconFgOver }};
|
||||
|
||||
rippleAreaPosition: point(4px, 4px);
|
||||
rippleAreaSize: 40px;
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h" // InformBox
|
||||
#include "editor/editor_layer_widget.h"
|
||||
#include "editor/photo_editor.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "window/window_controller.h"
|
||||
@@ -42,10 +43,11 @@ void OpenWithPreparedFile(
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sideLimit = PhotoSideLimit();
|
||||
auto callback = [=, done = std::move(doneCallback)](
|
||||
const PhotoModifications &mods) {
|
||||
image->modifications = mods;
|
||||
Storage::UpdateImageDetails(*file, previewWidth);
|
||||
Storage::UpdateImageDetails(*file, previewWidth, sideLimit);
|
||||
{
|
||||
using namespace Ui;
|
||||
const auto size = file->preview.size();
|
||||
|
||||
@@ -490,8 +490,6 @@ void ApiWrap::requestSplitRanges() {
|
||||
_splits.push_back(MTP_messageRange(
|
||||
MTP_int(1),
|
||||
MTP_int(std::numeric_limits<int>::max())));
|
||||
} else {
|
||||
ranges::reverse(_splits);
|
||||
}
|
||||
_startProcess->splitIndex = useOnlyLastSplit()
|
||||
? (_splits.size() - 1)
|
||||
|
||||
@@ -294,14 +294,6 @@ InnerWidget::InnerWidget(
|
||||
view->itemDataChanged();
|
||||
}
|
||||
}, lifetime());
|
||||
session().data().animationPlayInlineRequest(
|
||||
) | rpl::start_with_next([=](auto item) {
|
||||
if (const auto view = viewForItem(item)) {
|
||||
if (const auto media = view->media()) {
|
||||
media->playAnimation();
|
||||
}
|
||||
}
|
||||
}, lifetime());
|
||||
session().data().itemVisibilityQueries(
|
||||
) | rpl::filter([=](
|
||||
const Data::Session::ItemVisibilityQuery &query) {
|
||||
@@ -1362,9 +1354,7 @@ void InnerWidget::copyContextImage(not_null<PhotoData*> photo) {
|
||||
if (photo->isNull() || !media || !media->loaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto image = media->image(Data::PhotoSize::Large)->original();
|
||||
QGuiApplication::clipboard()->setImage(image);
|
||||
media->setToClipboard();
|
||||
}
|
||||
|
||||
void InnerWidget::copySelectedText() {
|
||||
|
||||
@@ -845,18 +845,17 @@ not_null<HistoryItem*> History::addNewToBack(
|
||||
addItemToBlock(item);
|
||||
|
||||
if (!unread && item->isRegular()) {
|
||||
if (const auto sharedMediaTypes = item->sharedMediaTypes()) {
|
||||
if (const auto types = item->sharedMediaTypes()) {
|
||||
auto from = loadedAtTop() ? 0 : minMsgId();
|
||||
auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
|
||||
auto &storage = session().storage();
|
||||
storage.add(Storage::SharedMediaAddExisting(
|
||||
peer->id,
|
||||
MsgId(0), // topicRootId
|
||||
sharedMediaTypes,
|
||||
types,
|
||||
item->id,
|
||||
{ from, till }));
|
||||
const auto pinned = sharedMediaTypes.test(
|
||||
Storage::SharedMediaType::Pinned);
|
||||
const auto pinned = types.test(Storage::SharedMediaType::Pinned);
|
||||
if (pinned) {
|
||||
setHasPinnedMessages(true);
|
||||
}
|
||||
@@ -864,7 +863,7 @@ not_null<HistoryItem*> History::addNewToBack(
|
||||
storage.add(Storage::SharedMediaAddExisting(
|
||||
peer->id,
|
||||
topic->rootId(),
|
||||
sharedMediaTypes,
|
||||
types,
|
||||
item->id,
|
||||
{ item->id, item->id}));
|
||||
if (pinned) {
|
||||
|
||||
@@ -2638,8 +2638,7 @@ void HistoryInner::copyContextImage(
|
||||
if (photo->isNull() || !media || !media->loaded()) {
|
||||
return;
|
||||
} else if (!showCopyMediaRestriction(item)) {
|
||||
const auto image = media->image(Data::PhotoSize::Large)->original();
|
||||
QGuiApplication::clipboard()->setImage(image);
|
||||
media->setToClipboard();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1395,6 +1395,7 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) {
|
||||
setReplyMarkup(base::take(edition.replyMarkup));
|
||||
}
|
||||
if (!isLocalUpdateMedia()) {
|
||||
removeFromSharedMediaIndex();
|
||||
refreshMedia(edition.mtpMedia);
|
||||
}
|
||||
if (!edition.useSameReactions) {
|
||||
@@ -1405,6 +1406,9 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) {
|
||||
setText(_media
|
||||
? edition.textWithEntities
|
||||
: EnsureNonEmpty(edition.textWithEntities));
|
||||
if (!isLocalUpdateMedia()) {
|
||||
indexAsNewItem();
|
||||
}
|
||||
if (!edition.useSameReplies) {
|
||||
if (!edition.replies.isNull) {
|
||||
if (checkRepliesPts(edition.replies)) {
|
||||
@@ -1424,6 +1428,7 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) {
|
||||
if (message.vaction().type() == mtpc_messageActionHistoryClear) {
|
||||
const auto wasGrouped = history()->owner().groups().isGrouped(this);
|
||||
setReplyMarkup({});
|
||||
removeFromSharedMediaIndex();
|
||||
refreshMedia(nullptr);
|
||||
setTextValue({});
|
||||
changeViewsCount(-1);
|
||||
@@ -1653,7 +1658,10 @@ void HistoryItem::destroyHistoryEntry() {
|
||||
|
||||
Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const {
|
||||
auto result = Storage::SharedMediaTypesMask {};
|
||||
if (const auto media = this->media()) {
|
||||
const auto media = _savedLocalEditMediaData
|
||||
? _savedLocalEditMediaData->media.get()
|
||||
: _media.get();
|
||||
if (media) {
|
||||
result.set(media->sharedMediaTypes());
|
||||
}
|
||||
if (hasTextLinks()) {
|
||||
@@ -1684,6 +1692,18 @@ void HistoryItem::indexAsNewItem() {
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::removeFromSharedMediaIndex() {
|
||||
if (isRegular()) {
|
||||
if (const auto types = sharedMediaTypes()) {
|
||||
_history->session().storage().remove(
|
||||
Storage::SharedMediaRemoveOne(
|
||||
_history->peer->id,
|
||||
types,
|
||||
id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::incrementReplyToTopCounter() {
|
||||
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||
changeReplyToTopCounter(reply, 1);
|
||||
@@ -3676,18 +3696,22 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
const auto duration = (period == 5)
|
||||
? u"5 seconds"_q
|
||||
: Ui::FormatTTL(period);
|
||||
if (const auto from = action.vauto_setting_from()) {
|
||||
if (const auto from = action.vauto_setting_from(); from && period) {
|
||||
if (const auto peer = _from->owner().peer(peerFromUser(*from))) {
|
||||
if (!peer->isSelf() && period) {
|
||||
result.text = tr::lng_action_ttl_global(
|
||||
result.text = (peer->id == peer->session().userPeerId())
|
||||
? tr::lng_action_ttl_global_me(
|
||||
tr::now,
|
||||
lt_duration,
|
||||
{ .text = duration },
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_action_ttl_global(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText(), // Link 1.
|
||||
Ui::Text::Link(peer->name(), 1), // Link 1.
|
||||
lt_duration,
|
||||
{ .text = duration },
|
||||
Ui::Text::WithEntities);
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (isPost()) {
|
||||
|
||||
@@ -346,6 +346,7 @@ public:
|
||||
[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const;
|
||||
|
||||
void indexAsNewItem();
|
||||
void removeFromSharedMediaIndex();
|
||||
|
||||
[[nodiscard]] QString notificationHeader() const;
|
||||
[[nodiscard]] TextWithEntities notificationText() const;
|
||||
|
||||
@@ -47,7 +47,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace {
|
||||
|
||||
const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_";
|
||||
constexpr auto kReplyBarAlpha = 230. / 255.;
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -283,9 +282,10 @@ bool HistoryMessageReply::updateData(
|
||||
}
|
||||
|
||||
if (replyToMsg) {
|
||||
const auto repaint = [=] { holder->customEmojiRepaint(); };
|
||||
const auto context = Core::MarkedTextContext{
|
||||
.session = &holder->history()->session(),
|
||||
.customEmojiRepaint = [=] { holder->customEmojiRepaint(); },
|
||||
.customEmojiRepaint = repaint,
|
||||
};
|
||||
replyToText.setMarkedText(
|
||||
st::messageTextStyle,
|
||||
@@ -312,9 +312,17 @@ bool HistoryMessageReply::updateData(
|
||||
? replyToMsg->from()->id
|
||||
: PeerId(0);
|
||||
}
|
||||
|
||||
const auto media = replyToMsg->media();
|
||||
if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) {
|
||||
spoiler = nullptr;
|
||||
} else if (!spoiler) {
|
||||
spoiler = std::make_unique<Ui::SpoilerAnimation>(repaint);
|
||||
}
|
||||
} else if (force) {
|
||||
replyToMsgId = 0;
|
||||
replyToColorKey = PeerId(0);
|
||||
spoiler = nullptr;
|
||||
}
|
||||
if (force) {
|
||||
holder->history()->owner().requestItemResize(holder);
|
||||
@@ -456,21 +464,22 @@ void HistoryMessageReply::paint(
|
||||
st::msgReplyBarSize.height(),
|
||||
w + 2 * x);
|
||||
const auto opacity = p.opacity();
|
||||
p.setOpacity(opacity * kReplyBarAlpha);
|
||||
p.setOpacity(opacity * kBarAlpha);
|
||||
p.fillRect(rbar, bar);
|
||||
p.setOpacity(opacity);
|
||||
}
|
||||
|
||||
if (w > st::msgReplyBarSkip) {
|
||||
if (replyToMsg) {
|
||||
auto hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false;
|
||||
const auto media = replyToMsg->media();
|
||||
auto hasPreview = media && media->hasReplyPreview();
|
||||
if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) {
|
||||
hasPreview = false;
|
||||
}
|
||||
auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
|
||||
|
||||
if (hasPreview) {
|
||||
if (const auto image = replyToMsg->media()->replyPreview()) {
|
||||
if (const auto image = media->replyPreview()) {
|
||||
auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x);
|
||||
const auto preview = image->pixSingle(
|
||||
image->size() / style::DevicePixelRatio(),
|
||||
@@ -482,6 +491,16 @@ void HistoryMessageReply::paint(
|
||||
.outer = to.size(),
|
||||
});
|
||||
p.drawPixmap(to.x(), to.y(), preview);
|
||||
if (spoiler) {
|
||||
holder->clearCustomEmojiRepaint();
|
||||
Ui::FillSpoilerRect(
|
||||
p,
|
||||
to,
|
||||
Ui::DefaultImageSpoiler().frame(
|
||||
spoiler->index(
|
||||
context.now,
|
||||
context.paused)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (w > st::msgReplyBarSkip + previewSkip) {
|
||||
|
||||
@@ -195,6 +195,8 @@ struct HistoryMessageReply
|
||||
Expects(replyToVia == nullptr);
|
||||
}
|
||||
|
||||
static constexpr auto kBarAlpha = 230. / 255.;
|
||||
|
||||
bool updateData(not_null<HistoryItem*> holder, bool force = false);
|
||||
|
||||
// Must be called before destructor.
|
||||
@@ -246,6 +248,7 @@ struct HistoryMessageReply
|
||||
WebPageId replyToWebPageId = 0;
|
||||
ReplyToMessagePointer replyToMsg;
|
||||
std::unique_ptr<HistoryMessageVia> replyToVia;
|
||||
std::unique_ptr<Ui::SpoilerAnimation> spoiler;
|
||||
ClickHandlerPtr replyToLnk;
|
||||
mutable Ui::Text::String replyToName, replyToText;
|
||||
mutable int replyToVersion = 0;
|
||||
|
||||
@@ -24,6 +24,10 @@ public:
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString url() const override {
|
||||
return _text;
|
||||
}
|
||||
|
||||
QString dragText() const override {
|
||||
return _text;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_permissions_box.h" // ShowAboutGigagroup.
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "core/mime_type.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "ui/emoji_config.h"
|
||||
@@ -126,7 +127,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "core/application.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/options.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "ui/boxes/report_box.h"
|
||||
#include "ui/chat/pinned_bar.h"
|
||||
@@ -171,13 +171,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtCore/QMimeData>
|
||||
|
||||
const char kOptionAutoScrollInactiveChat[] =
|
||||
"auto-scroll-inactive-chat";
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kMessagesPerPageFirst = 10;
|
||||
constexpr auto kMessagesPerPage = 10;
|
||||
constexpr auto kMessagesPerPageFirst = 30;
|
||||
constexpr auto kMessagesPerPage = 50;
|
||||
constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request
|
||||
constexpr auto kScrollToVoiceAfterScrolledMs = 1000;
|
||||
constexpr auto kSkipRepaintWhileScrollMs = 100;
|
||||
@@ -194,13 +191,6 @@ constexpr auto kCommonModifiers = 0
|
||||
| Qt::ControlModifier;
|
||||
const auto kPsaAboutPrefix = "cloud_lng_about_psa_";
|
||||
|
||||
base::options::toggle AutoScrollInactiveChat({
|
||||
.id = kOptionAutoScrollInactiveChat,
|
||||
.name = "Enable auto-scroll of inactive chat",
|
||||
.description = "Enable auto-scrolling chat for new messages, "
|
||||
"even when the window is not in focus.",
|
||||
});
|
||||
|
||||
[[nodiscard]] rpl::producer<PeerData*> ActivePeerValue(
|
||||
not_null<Window::SessionController*> controller) {
|
||||
return controller->activeChatValue(
|
||||
@@ -563,15 +553,6 @@ HistoryWidget::HistoryWidget(
|
||||
});
|
||||
}, lifetime());
|
||||
|
||||
session().data().animationPlayInlineRequest(
|
||||
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
|
||||
if (const auto view = item->mainView()) {
|
||||
if (const auto media = view->media()) {
|
||||
media->playAnimation();
|
||||
}
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
session().data().webPageUpdates(
|
||||
) | rpl::filter([=](not_null<WebPageData*> page) {
|
||||
return (_previewData == page.get());
|
||||
@@ -742,8 +723,9 @@ HistoryWidget::HistoryWidget(
|
||||
if (flags & PeerUpdateFlag::UnavailableReason) {
|
||||
const auto unavailable = _peer->computeUnavailableReason();
|
||||
if (!unavailable.isEmpty()) {
|
||||
const auto account = &_peer->account();
|
||||
closeCurrent();
|
||||
if (const auto primary = Core::App().primaryWindow()) {
|
||||
if (const auto primary = Core::App().windowFor(account)) {
|
||||
primary->show(Ui::MakeInformBox(unavailable));
|
||||
}
|
||||
return;
|
||||
@@ -1971,7 +1953,9 @@ void HistoryWidget::applyCloudDraft(History *history) {
|
||||
}
|
||||
|
||||
bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
|
||||
if (session().supportMode()) {
|
||||
Expects(_history != nullptr);
|
||||
|
||||
if (session().supportMode() || !_history->trackUnreadMessages()) {
|
||||
return true;
|
||||
} else if (!_historyInited) {
|
||||
return false;
|
||||
@@ -1979,6 +1963,14 @@ bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
|
||||
_history->calculateFirstUnreadMessage();
|
||||
const auto unread = _history->firstUnreadMessage();
|
||||
const auto visibleBottom = _scroll->scrollTop() + _scroll->height();
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
|
||||
"unread: %4, top: %5, visibleBottom: %6."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(unread ? unread->data()->id.bare : 0
|
||||
).arg(unread ? _list->itemTop(unread) : -1
|
||||
).arg(visibleBottom));
|
||||
return unread && _list->itemTop(unread) <= visibleBottom;
|
||||
}
|
||||
|
||||
@@ -2004,6 +1996,11 @@ void HistoryWidget::showHistory(
|
||||
|
||||
if (showAtMsgId == ShowAtUnreadMsgId
|
||||
&& insideJumpToEndInsteadOfToUnread()) {
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
|
||||
"Jumping to end instead of unread."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())));
|
||||
showAtMsgId = ShowAtTheEndMsgId;
|
||||
} else if (showAtMsgId == ShowForChooseMessagesMsgId) {
|
||||
if (_chooseForReport) {
|
||||
@@ -2023,6 +2020,11 @@ void HistoryWidget::showHistory(
|
||||
}
|
||||
const auto canShowNow = _history->isReadyFor(showAtMsgId);
|
||||
if (!canShowNow) {
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Showing delayed at %4."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(showAtMsgId.bare));
|
||||
delayedShowAt(showAtMsgId);
|
||||
} else {
|
||||
_history->forgetScrollState();
|
||||
@@ -2042,6 +2044,13 @@ void HistoryWidget::showHistory(
|
||||
|
||||
setMsgId(showAtMsgId);
|
||||
if (_historyInited) {
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
|
||||
"Showing instant at %4."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(showAtMsgId.bare));
|
||||
|
||||
const auto to = countInitialScrollTop();
|
||||
const auto item = getItemFromHistoryOrMigrated(
|
||||
_showAtMsgId);
|
||||
@@ -2179,6 +2188,11 @@ void HistoryWidget::showHistory(
|
||||
} else {
|
||||
_chooseForReport = nullptr;
|
||||
}
|
||||
if (_showAtMsgId == ShowAtUnreadMsgId
|
||||
&& !_history->trackUnreadMessages()
|
||||
&& !hasSavedScroll()) {
|
||||
_showAtMsgId = ShowAtTheEndMsgId;
|
||||
}
|
||||
refreshTopBarActiveChat();
|
||||
updateTopBarSelection();
|
||||
|
||||
@@ -2966,11 +2980,12 @@ void HistoryWidget::unreadCountUpdated() {
|
||||
crl::on_main(this, [=, history = _history] {
|
||||
if (history == _history) {
|
||||
closeCurrent();
|
||||
_cancelRequests.fire({});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_cornerButtons.updateJumpDownVisibility(_history->isForum()
|
||||
const auto hideCounter = _history->isForum()
|
||||
|| !_history->trackUnreadMessages();
|
||||
_cornerButtons.updateJumpDownVisibility(hideCounter
|
||||
? 0
|
||||
: _history->chatListBadgesState().unreadCounter);
|
||||
}
|
||||
@@ -2994,7 +3009,7 @@ void HistoryWidget::messagesFailed(const MTP::Error &error, int requestId) {
|
||||
|| error.type() == u"USER_BANNED_IN_CHANNEL"_q) {
|
||||
auto was = _peer;
|
||||
closeCurrent();
|
||||
if (const auto primary = Core::App().primaryWindow()) {
|
||||
if (const auto primary = Core::App().windowFor(&was->account())) {
|
||||
Ui::ShowMultilineToast({
|
||||
.parentOverride = Window::Show(primary).toastParent(),
|
||||
.text = { (was && was->isMegagroup())
|
||||
@@ -3266,6 +3281,12 @@ void HistoryWidget::loadMessages() {
|
||||
const auto minId = 0;
|
||||
const auto historyHash = uint64(0);
|
||||
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Loading up before %4."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(offsetId.bare));
|
||||
|
||||
const auto history = from;
|
||||
const auto type = Data::Histories::RequestType::History;
|
||||
auto &histories = history->owner().histories();
|
||||
@@ -3318,6 +3339,12 @@ void HistoryWidget::loadMessagesDown() {
|
||||
const auto minId = 0;
|
||||
const auto historyHash = uint64(0);
|
||||
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Loading down after %4."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(offsetId.bare));
|
||||
|
||||
const auto history = from;
|
||||
const auto type = Data::Histories::RequestType::History;
|
||||
auto &histories = history->owner().histories();
|
||||
@@ -3350,6 +3377,12 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
|
||||
clearAllLoadRequests();
|
||||
_delayedShowAtMsgId = showAtMsgId;
|
||||
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Loading delayed around %4."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(showAtMsgId.bare));
|
||||
|
||||
auto from = _history;
|
||||
auto offsetId = MsgId();
|
||||
auto offset = 0;
|
||||
@@ -4028,6 +4061,10 @@ void HistoryWidget::doneShow() {
|
||||
void HistoryWidget::cornerButtonsShowAtPosition(
|
||||
Data::MessagePosition position) {
|
||||
if (position == Data::UnreadMessagePosition) {
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Show at unread requested."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())));
|
||||
showHistory(_peer->id, ShowAtUnreadMsgId);
|
||||
} else if (_peer && position.fullId.peer == _peer->id) {
|
||||
showHistory(_peer->id, position.fullId.msg);
|
||||
@@ -4499,39 +4536,52 @@ bool HistoryWidget::updateCmdStartShown() {
|
||||
}
|
||||
|
||||
void HistoryWidget::searchInChat() {
|
||||
if (_history) {
|
||||
controller()->content()->searchInChat(_history);
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::searchInChatEmbedded(std::optional<QString> query) {
|
||||
if (!_history) {
|
||||
return;
|
||||
} else if (controller()->isPrimary()) {
|
||||
controller()->content()->searchInChat(_history);
|
||||
} else if (!_composeSearch) {
|
||||
const auto search = [=] {
|
||||
const auto update = [=] {
|
||||
updateControlsVisibility();
|
||||
updateBotKeyboard();
|
||||
updateFieldPlaceholder();
|
||||
|
||||
updateControlsGeometry();
|
||||
};
|
||||
_composeSearch = std::make_unique<HistoryView::ComposeSearch>(
|
||||
this,
|
||||
controller(),
|
||||
_history);
|
||||
|
||||
update();
|
||||
setInnerFocus();
|
||||
_composeSearch->destroyRequests(
|
||||
) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=] {
|
||||
_composeSearch = nullptr;
|
||||
|
||||
update();
|
||||
setInnerFocus();
|
||||
}, _composeSearch->lifetime());
|
||||
};
|
||||
if (!preventsClose(search)) {
|
||||
search();
|
||||
} else if (_composeSearch) {
|
||||
if (query) {
|
||||
_composeSearch->setQuery(*query);
|
||||
}
|
||||
_composeSearch->setInnerFocus();
|
||||
return;
|
||||
}
|
||||
const auto search = crl::guard(_list, [=] {
|
||||
if (!_history) {
|
||||
return;
|
||||
}
|
||||
const auto update = [=] {
|
||||
updateControlsVisibility();
|
||||
updateBotKeyboard();
|
||||
updateFieldPlaceholder();
|
||||
|
||||
updateControlsGeometry();
|
||||
};
|
||||
_composeSearch = std::make_unique<HistoryView::ComposeSearch>(
|
||||
this,
|
||||
controller(),
|
||||
_history,
|
||||
query.value_or(QString()));
|
||||
|
||||
update();
|
||||
setInnerFocus();
|
||||
_composeSearch->destroyRequests(
|
||||
) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=] {
|
||||
_composeSearch = nullptr;
|
||||
|
||||
update();
|
||||
setInnerFocus();
|
||||
}, _composeSearch->lifetime());
|
||||
});
|
||||
if (!preventsClose(search)) {
|
||||
search();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5144,16 +5194,13 @@ bool HistoryWidget::confirmSendingFiles(
|
||||
}
|
||||
}
|
||||
|
||||
if (hasImage) {
|
||||
auto image = qvariant_cast<QImage>(data->imageData());
|
||||
if (!image.isNull()) {
|
||||
confirmSendingFiles(
|
||||
std::move(image),
|
||||
QByteArray(),
|
||||
overrideSendImagesAsPhotos,
|
||||
insertTextOnCancel);
|
||||
return true;
|
||||
}
|
||||
if (auto read = Core::ReadMimeImage(data)) {
|
||||
confirmSendingFiles(
|
||||
std::move(read.image),
|
||||
std::move(read.content),
|
||||
overrideSendImagesAsPhotos,
|
||||
insertTextOnCancel);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -5348,8 +5395,15 @@ MsgId HistoryWidget::replyToId() const {
|
||||
return _replyToId ? _replyToId : (_kbReplyTo ? _kbReplyTo->id : 0);
|
||||
}
|
||||
|
||||
bool HistoryWidget::hasSavedScroll() const {
|
||||
Expects(_history != nullptr);
|
||||
|
||||
return _history->scrollTopItem
|
||||
|| (_migrated && _migrated->scrollTopItem);
|
||||
}
|
||||
|
||||
int HistoryWidget::countInitialScrollTop() {
|
||||
if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem)) {
|
||||
if (hasSavedScroll()) {
|
||||
return _list->historyScrollTop();
|
||||
} else if (_showAtMsgId
|
||||
&& (IsServerMsgId(_showAtMsgId)
|
||||
@@ -5413,9 +5467,6 @@ int HistoryWidget::countAutomaticScrollTop() {
|
||||
Expects(_list != nullptr);
|
||||
|
||||
if (const auto unread = _history->firstUnreadMessage()) {
|
||||
if (AutoScrollInactiveChat.value()) {
|
||||
return ScrollMax;
|
||||
}
|
||||
const auto firstUnreadTop = _list->itemTop(unread);
|
||||
const auto possibleUnreadBarTop = _scroll->scrollTopMax()
|
||||
+ HistoryView::UnreadBar::height()
|
||||
@@ -5866,7 +5917,10 @@ std::optional<bool> HistoryWidget::cornerButtonsDownShown() {
|
||||
}
|
||||
|
||||
const auto haveUnreadBelowBottom = [&](History *history) {
|
||||
if (!_list || !history || history->unreadCount() <= 0) {
|
||||
if (!_list
|
||||
|| !history
|
||||
|| history->unreadCount() <= 0
|
||||
|| !history->trackUnreadMessages()) {
|
||||
return false;
|
||||
}
|
||||
const auto unread = history->firstUnreadMessage();
|
||||
@@ -5922,7 +5976,6 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->key() == Qt::Key_Escape) {
|
||||
e->ignore();
|
||||
} else if (e->key() == Qt::Key_Back) {
|
||||
controller()->showBackFromStack();
|
||||
_cancelRequests.fire({});
|
||||
} else if (e->key() == Qt::Key_PageDown) {
|
||||
_scroll->keyPressEvent(e);
|
||||
@@ -6224,7 +6277,7 @@ void HistoryWidget::checkPinnedBarState() {
|
||||
_pinnedBar = std::make_unique<Ui::PinnedBar>(this, [=] {
|
||||
return controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Any);
|
||||
});
|
||||
}, controller()->gifPauseLevelChanged());
|
||||
auto pinnedRefreshed = Info::Profile::SharedMediaCountValue(
|
||||
_peer,
|
||||
MsgId(0), // topicRootId
|
||||
@@ -7490,20 +7543,44 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
|
||||
p.setInactive(
|
||||
controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any));
|
||||
p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg);
|
||||
|
||||
const auto media = (!drawWebPagePreview && drawMsgText)
|
||||
? drawMsgText->media()
|
||||
: nullptr;
|
||||
const auto hasPreview = media && media->hasReplyPreview();
|
||||
const auto preview = hasPreview ? media->replyPreview() : nullptr;
|
||||
const auto spoilered = preview && media->hasSpoiler();
|
||||
if (!spoilered) {
|
||||
_replySpoiler = nullptr;
|
||||
} else if (!_replySpoiler) {
|
||||
_replySpoiler = std::make_unique<Ui::SpoilerAnimation>([=] {
|
||||
updateField();
|
||||
});
|
||||
}
|
||||
|
||||
if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
|
||||
const auto now = crl::now();
|
||||
const auto paused = p.inactive();
|
||||
auto replyLeft = st::historyReplySkip;
|
||||
(_editMsgId ? st::historyEditIcon : st::historyReplyIcon).paint(p, st::historyReplyIconPosition + QPoint(0, backy), width());
|
||||
if (!drawWebPagePreview) {
|
||||
if (drawMsgText) {
|
||||
if (drawMsgText->media() && drawMsgText->media()->hasReplyPreview()) {
|
||||
if (const auto image = drawMsgText->media()->replyPreview()) {
|
||||
if (hasPreview) {
|
||||
if (preview) {
|
||||
auto to = QRect(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
|
||||
p.drawPixmap(to.x(), to.y(), image->pixSingle(
|
||||
image->size() / style::DevicePixelRatio(),
|
||||
p.drawPixmap(to.x(), to.y(), preview->pixSingle(
|
||||
preview->size() / style::DevicePixelRatio(),
|
||||
{
|
||||
.options = Images::Option::RoundSmall,
|
||||
.outer = to.size(),
|
||||
}));
|
||||
if (_replySpoiler) {
|
||||
Ui::FillSpoilerRect(
|
||||
p,
|
||||
to,
|
||||
Ui::DefaultImageSpoiler().frame(
|
||||
_replySpoiler->index(now, paused)));
|
||||
}
|
||||
}
|
||||
replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
|
||||
}
|
||||
@@ -7521,8 +7598,8 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
|
||||
.availableWidth = width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right(),
|
||||
.palette = &st::historyComposeAreaPalette,
|
||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||
.now = crl::now(),
|
||||
.paused = p.inactive(),
|
||||
.now = now,
|
||||
.paused = paused,
|
||||
.elisionLines = 1,
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -70,6 +70,7 @@ class RequestsBar;
|
||||
struct PreparedList;
|
||||
class SendFilesWay;
|
||||
class SendAsButton;
|
||||
class SpoilerAnimation;
|
||||
enum class ReportReason;
|
||||
class ChooseThemeController;
|
||||
class ContinuousScroll;
|
||||
@@ -102,8 +103,6 @@ class TTLButton;
|
||||
class BotKeyboard;
|
||||
class HistoryInner;
|
||||
|
||||
extern const char kOptionAutoScrollInactiveChat[];
|
||||
|
||||
class HistoryWidget final
|
||||
: public Window::AbstractSectionWidget
|
||||
, private HistoryView::CornerButtonsDelegate {
|
||||
@@ -236,6 +235,7 @@ public:
|
||||
[[nodiscard]] rpl::producer<> cancelRequests() const {
|
||||
return _cancelRequests.events();
|
||||
}
|
||||
void searchInChatEmbedded(std::optional<QString> query = {});
|
||||
|
||||
void updateNotifyControls();
|
||||
|
||||
@@ -573,6 +573,7 @@ private:
|
||||
|
||||
// when scroll position or scroll area size changed this method
|
||||
// updates the boundings of the visible area in HistoryInner
|
||||
[[nodiscard]] bool hasSavedScroll() const;
|
||||
void visibleAreaUpdated();
|
||||
int countInitialScrollTop();
|
||||
int countAutomaticScrollTop();
|
||||
@@ -626,6 +627,7 @@ private:
|
||||
|
||||
HistoryItem *_replyEditMsg = nullptr;
|
||||
Ui::Text::String _replyEditMsgText;
|
||||
std::unique_ptr<Ui::SpoilerAnimation> _replySpoiler;
|
||||
mutable base::Timer _updateEditTimeLeftDisplay;
|
||||
|
||||
object_ptr<Ui::IconButton> _fieldBarCancel;
|
||||
|
||||
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_web_page.h"
|
||||
@@ -987,12 +988,12 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
|
||||
updateControlsGeometry(_wrap->size());
|
||||
updateControlsVisibility();
|
||||
updateFieldPlaceholder();
|
||||
updateSendAsButton();
|
||||
updateAttachBotsMenu();
|
||||
//if (!_history) {
|
||||
// return;
|
||||
//}
|
||||
const auto peer = _history->peer;
|
||||
initSendAsButton(peer);
|
||||
if (peer->isChat() && peer->asChat()->noParticipantInfo()) {
|
||||
session().api().requestFullPeer(peer);
|
||||
} else if (const auto channel = peer->asMegagroup()) {
|
||||
@@ -1342,7 +1343,6 @@ void ComposeControls::init() {
|
||||
initField();
|
||||
initTabbedSelector();
|
||||
initSendButton();
|
||||
initSendAsButton();
|
||||
initWriteRestriction();
|
||||
initVoiceRecordBar();
|
||||
initKeyHandler();
|
||||
@@ -2058,17 +2058,24 @@ void ComposeControls::initSendButton() {
|
||||
SendMenu::DefaultScheduleCallback(_wrap.get(), sendMenuType(), send));
|
||||
}
|
||||
|
||||
void ComposeControls::initSendAsButton() {
|
||||
session().sendAsPeers().updated(
|
||||
) | rpl::filter([=](not_null<PeerData*> peer) {
|
||||
return _history && (peer == _history->peer);
|
||||
}) | rpl::start_with_next([=] {
|
||||
void ComposeControls::initSendAsButton(not_null<PeerData*> peer) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
// SendAsPeers::shouldChoose checks PeerData::canWrite(false).
|
||||
rpl::combine(
|
||||
rpl::single(peer) | rpl::then(
|
||||
session().sendAsPeers().updated() | rpl::filter(_1 == peer)
|
||||
),
|
||||
Data::CanWriteValue(peer, false)
|
||||
) | rpl::skip(1) | rpl::start_with_next([=] {
|
||||
if (updateSendAsButton()) {
|
||||
updateControlsVisibility();
|
||||
updateControlsGeometry(_wrap->size());
|
||||
orderControls();
|
||||
}
|
||||
}, _wrap->lifetime());
|
||||
|
||||
updateSendAsButton();
|
||||
}
|
||||
|
||||
void ComposeControls::cancelInlineBot() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user