Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e206f42e4e | ||
|
|
3f60410190 | ||
|
|
3cdd8558db | ||
|
|
f2b89445ae | ||
|
|
3ff4bf77e7 | ||
|
|
d16ccf0d9e | ||
|
|
fc7d9b264f | ||
|
|
b28d5a63d1 | ||
|
|
043ba4ff04 | ||
|
|
d6c3bf4168 | ||
|
|
7bf7a8feff | ||
|
|
3413ad1d22 | ||
|
|
84af084a3b | ||
|
|
cd50008429 | ||
|
|
767459ab57 | ||
|
|
3b45a120e6 | ||
|
|
b04aaba8d0 | ||
|
|
bfa3655c7b | ||
|
|
8642eb23a7 | ||
|
|
ef8ecc546b | ||
|
|
fafbbb4996 | ||
|
|
3a021f4e49 | ||
|
|
2788c19c85 | ||
|
|
300cc3dbca | ||
|
|
62516e264d | ||
|
|
8b89cfc4cb | ||
|
|
e3f65d2346 | ||
|
|
2b383a4236 | ||
|
|
a8426bd6da | ||
|
|
39a02e649d | ||
|
|
276fe2169a | ||
|
|
8fae56bee8 | ||
|
|
0e16a50bbc | ||
|
|
9de372d715 | ||
|
|
fbae5bdbcf | ||
|
|
aee1ef78da | ||
|
|
27c5c4b8f2 | ||
|
|
94e06c6846 | ||
|
|
7948d971e8 | ||
|
|
70acc7a0e3 | ||
|
|
56fdc7d39a | ||
|
|
f67c3bbf65 | ||
|
|
de194c4aa2 | ||
|
|
511805199f | ||
|
|
aa241a1f62 | ||
|
|
4125a45503 | ||
|
|
1349989494 | ||
|
|
639ed8b973 | ||
|
|
2f5db08c9b | ||
|
|
4c6814def6 | ||
|
|
387914be31 | ||
|
|
2f2003c89b | ||
|
|
48589b721d | ||
|
|
3bdf1634a9 | ||
|
|
1878061c9a | ||
|
|
774c3b5ba0 | ||
|
|
a64b8d4181 | ||
|
|
e3e380124d | ||
|
|
823fc25fa8 | ||
|
|
4062912a98 | ||
|
|
62b5192f24 | ||
|
|
058717532a | ||
|
|
d117a72e6e | ||
|
|
3ba5b825e5 | ||
|
|
075ab20e5b | ||
|
|
deeea0aaed | ||
|
|
8113117cc4 | ||
|
|
7bfe096f3b |
@@ -990,6 +990,8 @@ PRIVATE
|
||||
platform/linux/notifications_manager_linux.h
|
||||
platform/linux/specific_linux.cpp
|
||||
platform/linux/specific_linux.h
|
||||
platform/linux/tray_linux.cpp
|
||||
platform/linux/tray_linux.h
|
||||
platform/mac/file_utilities_mac.mm
|
||||
platform/mac/file_utilities_mac.h
|
||||
platform/mac/launcher_mac.mm
|
||||
@@ -1005,6 +1007,8 @@ PRIVATE
|
||||
platform/mac/specific_mac.h
|
||||
platform/mac/specific_mac_p.mm
|
||||
platform/mac/specific_mac_p.h
|
||||
platform/mac/tray_mac.mm
|
||||
platform/mac/tray_mac.h
|
||||
platform/mac/window_title_mac.mm
|
||||
platform/mac/touchbar/items/mac_formatter_item.h
|
||||
platform/mac/touchbar/items/mac_formatter_item.mm
|
||||
@@ -1038,6 +1042,8 @@ PRIVATE
|
||||
platform/win/notifications_manager_win.h
|
||||
platform/win/specific_win.cpp
|
||||
platform/win/specific_win.h
|
||||
platform/win/tray_win.cpp
|
||||
platform/win/tray_win.h
|
||||
platform/win/windows_app_user_model_id.cpp
|
||||
platform/win/windows_app_user_model_id.h
|
||||
platform/win/windows_dlls.cpp
|
||||
@@ -1054,6 +1060,7 @@ PRIVATE
|
||||
platform/platform_main_window.h
|
||||
platform/platform_notifications_manager.h
|
||||
platform/platform_specific.h
|
||||
platform/platform_tray.h
|
||||
platform/platform_window_title.h
|
||||
profile/profile_back_button.cpp
|
||||
profile/profile_back_button.h
|
||||
@@ -1067,6 +1074,8 @@ PRIVATE
|
||||
profile/profile_cover_drop_area.h
|
||||
settings/settings_advanced.cpp
|
||||
settings/settings_advanced.h
|
||||
settings/settings_blocked_peers.cpp
|
||||
settings/settings_blocked_peers.h
|
||||
settings/settings_chat.cpp
|
||||
settings/settings_chat.h
|
||||
settings/settings_calls.cpp
|
||||
@@ -1258,6 +1267,8 @@ PRIVATE
|
||||
settings.cpp
|
||||
settings.h
|
||||
stdafx.h
|
||||
tray.cpp
|
||||
tray.h
|
||||
)
|
||||
|
||||
if (NOT build_winstore)
|
||||
|
||||
BIN
Telegram/Resources/animations/blocked_peers_empty.tgs
Normal file
1
Telegram/Resources/animations/filters.tgs
Normal file
BIN
Telegram/Resources/icons/folders/folders_airplane.png
Normal file
|
After Width: | Height: | Size: 635 B |
BIN
Telegram/Resources/icons/folders/folders_airplane@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/folders/folders_airplane@3x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Telegram/Resources/icons/folders/folders_book.png
Normal file
|
After Width: | Height: | Size: 456 B |
BIN
Telegram/Resources/icons/folders/folders_book@2x.png
Normal file
|
After Width: | Height: | Size: 837 B |
BIN
Telegram/Resources/icons/folders/folders_book@3x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/folders/folders_light.png
Normal file
|
After Width: | Height: | Size: 691 B |
BIN
Telegram/Resources/icons/folders/folders_light@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/folders/folders_light@3x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Telegram/Resources/icons/folders/folders_like.png
Normal file
|
After Width: | Height: | Size: 797 B |
BIN
Telegram/Resources/icons/folders/folders_like@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/folders/folders_like@3x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Telegram/Resources/icons/folders/folders_money.png
Normal file
|
After Width: | Height: | Size: 774 B |
BIN
Telegram/Resources/icons/folders/folders_money@2x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/folders/folders_money@3x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Telegram/Resources/icons/folders/folders_note.png
Normal file
|
After Width: | Height: | Size: 495 B |
BIN
Telegram/Resources/icons/folders/folders_note@2x.png
Normal file
|
After Width: | Height: | Size: 823 B |
BIN
Telegram/Resources/icons/folders/folders_note@3x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/folders/folders_palette.png
Normal file
|
After Width: | Height: | Size: 689 B |
BIN
Telegram/Resources/icons/folders/folders_palette@2x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/folders/folders_palette@3x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Telegram/Resources/icons/folders/folders_poo.png
Normal file
|
After Width: | Height: | Size: 828 B |
BIN
Telegram/Resources/icons/folders/folders_poo@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/folders/folders_poo@3x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 469 B |
|
After Width: | Height: | Size: 745 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 665 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Resources/icons/settings/blocked.png
Normal file
|
After Width: | Height: | Size: 476 B |
BIN
Telegram/Resources/icons/settings/blocked@2x.png
Normal file
|
After Width: | Height: | Size: 789 B |
BIN
Telegram/Resources/icons/settings/blocked@3x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/settings/remove.png
Normal file
|
After Width: | Height: | Size: 193 B |
BIN
Telegram/Resources/icons/settings/remove@2x.png
Normal file
|
After Width: | Height: | Size: 233 B |
BIN
Telegram/Resources/icons/settings/remove@3x.png
Normal file
|
After Width: | Height: | Size: 294 B |
@@ -799,6 +799,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_blocked_list_confirm_text" = "Do you want to block {name} from messaging and calling you on Telegram?";
|
||||
"lng_blocked_list_confirm_clear" = "Delete this chat";
|
||||
"lng_blocked_list_confirm_ok" = "Block";
|
||||
"lng_blocked_list_empty_title" = "No blocked users";
|
||||
"lng_blocked_list_empty_description" = "You haven't blocked anyone yet.";
|
||||
"lng_blocked_list_subtitle#one" = "{count} blocked user";
|
||||
"lng_blocked_list_subtitle#other" = "{count} blocked users";
|
||||
|
||||
"lng_edit_privacy_everyone" = "Everybody";
|
||||
"lng_edit_privacy_contacts" = "My contacts";
|
||||
|
||||
@@ -2,4 +2,10 @@
|
||||
<qresource prefix="/animations">
|
||||
<file alias="change_number.tgs">../../animations/change_number.tgs</file>
|
||||
</qresource>
|
||||
<qresource prefix="/animations">
|
||||
<file alias="blocked_peers_empty.tgs">../../animations/blocked_peers_empty.tgs</file>
|
||||
</qresource>
|
||||
<qresource prefix="/animations">
|
||||
<file alias="filters.tgs">../../animations/filters.tgs</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="3.7.2.0" />
|
||||
Version="3.7.4.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,7,2,0
|
||||
PRODUCTVERSION 3,7,2,0
|
||||
FILEVERSION 3,7,4,0
|
||||
PRODUCTVERSION 3,7,4,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "3.7.2.0"
|
||||
VALUE "FileVersion", "3.7.4.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.7.2.0"
|
||||
VALUE "ProductVersion", "3.7.4.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,7,2,0
|
||||
PRODUCTVERSION 3,7,2,0
|
||||
FILEVERSION 3,7,4,0
|
||||
PRODUCTVERSION 3,7,4,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", "3.7.2.0"
|
||||
VALUE "FileVersion", "3.7.4.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.7.2.0"
|
||||
VALUE "ProductVersion", "3.7.4.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -83,7 +83,7 @@ MTPInputMedia PrepareUploadedPhoto(RemoteFileInfo info) {
|
||||
MTP_flags(flags),
|
||||
info.file,
|
||||
MTP_vector<MTPInputDocument>(
|
||||
ranges::to<QVector>(info.attachedStickers)),
|
||||
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
|
||||
MTP_int(0));
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ MTPInputMedia PrepareUploadedDocument(
|
||||
MTP_string(document->mimeString()),
|
||||
ComposeSendingDocumentAttributes(document),
|
||||
MTP_vector<MTPInputDocument>(
|
||||
ranges::to<QVector>(info.attachedStickers)),
|
||||
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
|
||||
MTP_int(0));
|
||||
}
|
||||
|
||||
|
||||
@@ -1393,6 +1393,18 @@ void EditNameBox::prepare() {
|
||||
|
||||
connect(_first, &Ui::InputField::submitted, [=] { submit(); });
|
||||
connect(_last, &Ui::InputField::submitted, [=] { submit(); });
|
||||
|
||||
_first->customTab(true);
|
||||
_last->customTab(true);
|
||||
|
||||
QObject::connect(
|
||||
_first,
|
||||
&Ui::InputField::tabbed,
|
||||
[=] { _last->setFocus(); });
|
||||
QObject::connect(
|
||||
_last,
|
||||
&Ui::InputField::tabbed,
|
||||
[=] { _first->setFocus(); });
|
||||
}
|
||||
|
||||
void EditNameBox::setInnerFocus() {
|
||||
|
||||
@@ -602,6 +602,9 @@ changePhoneError: FlatLabel(changePhoneLabel) {
|
||||
textFg: boxTextFgError;
|
||||
}
|
||||
|
||||
blockedUsersListSubtitleAddPadding: margins(0px, 1px, 0px, -14px);
|
||||
blockedUsersListIconPadding: margins(0px, 34px, 0px, 5px);
|
||||
|
||||
adminLogFilterUserpicLeft: 15px;
|
||||
adminLogFilterLittleSkip: 16px;
|
||||
adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) {
|
||||
|
||||
@@ -510,7 +510,7 @@ void ChangePhone::setupContent() {
|
||||
}
|
||||
|
||||
void ChangePhone::showFinished() {
|
||||
_animate();
|
||||
_animate(anim::repeat::loop);
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
|
||||
@@ -36,7 +36,7 @@ private:
|
||||
void setupContent();
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
Fn<void()> _animate;
|
||||
Fn<void(anim::repeat)> _animate;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/filter_icons.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h" // Ui::Text::Bold
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -27,46 +26,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
class FolderButton : public Ui::SettingsButton {
|
||||
public:
|
||||
FolderButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const Data::ChatFilter &filter);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
const Ui::FilterIcon _icon;
|
||||
|
||||
};
|
||||
|
||||
FolderButton::FolderButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const Data::ChatFilter &filter)
|
||||
: SettingsButton(
|
||||
parent,
|
||||
rpl::single(filter.title()),
|
||||
st::paymentsSectionButton)
|
||||
, _icon(Ui::ComputeFilterIcon(filter)) {
|
||||
}
|
||||
|
||||
void FolderButton::paintEvent(QPaintEvent *e) {
|
||||
SettingsButton::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
const auto over = isOver() || isDown();
|
||||
const auto icon = Ui::LookupFilterIcon(_icon).normal;
|
||||
icon->paint(
|
||||
p,
|
||||
st::settingsFilterIconLeft,
|
||||
(height() - icon->height()) / 2,
|
||||
width(),
|
||||
(over
|
||||
? st::dialogsUnreadBgMutedOver
|
||||
: st::dialogsUnreadBgMuted)->c);
|
||||
}
|
||||
|
||||
Data::ChatFilter ChangedFilter(
|
||||
const Data::ChatFilter &filter,
|
||||
not_null<History*> history,
|
||||
@@ -165,47 +124,6 @@ void ChooseFilterValidator::remove(FilterId filterId) const {
|
||||
ChangeFilterById(filterId, _history, false);
|
||||
}
|
||||
|
||||
void ChooseFilterBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<History*> history) {
|
||||
box->setTitle(tr::lng_filters_add_box_title());
|
||||
|
||||
const auto validator = ChooseFilterValidator(history);
|
||||
|
||||
const auto container = box->verticalLayout()->add(
|
||||
object_ptr<Ui::VerticalLayout>(box->verticalLayout()));
|
||||
|
||||
const auto rebuild = [=] {
|
||||
while (container->count()) {
|
||||
delete container->widgetAt(0);
|
||||
}
|
||||
for (const auto &filter : history->owner().chatsFilters().list()) {
|
||||
if (filter.contains(history)) {
|
||||
continue;
|
||||
}
|
||||
container->add(
|
||||
object_ptr<FolderButton>(box, filter),
|
||||
style::margins()
|
||||
)->setClickedCallback([=, id = filter.id()] {
|
||||
validator.add(id);
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
container->resizeToWidth(box->verticalLayout()->width());
|
||||
if (!container->count()) {
|
||||
box->closeBox();
|
||||
}
|
||||
};
|
||||
|
||||
history->owner().chatsFilters().changed(
|
||||
) | rpl::start_with_next([=] {
|
||||
rebuild();
|
||||
}, box->lifetime());
|
||||
rebuild();
|
||||
|
||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
void FillChooseFilterMenu(
|
||||
not_null<Ui::PopupMenu*> menu,
|
||||
not_null<History*> history) {
|
||||
|
||||
@@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
@@ -29,10 +28,6 @@ private:
|
||||
|
||||
};
|
||||
|
||||
void ChooseFilterBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<History*> history);
|
||||
|
||||
void FillChooseFilterMenu(
|
||||
not_null<Ui::PopupMenu*> menu,
|
||||
not_null<History*> history);
|
||||
|
||||
@@ -500,6 +500,7 @@ void EditFilterBox(
|
||||
const Data::ChatFilter &filter,
|
||||
Fn<void(const Data::ChatFilter &)> doneCallback) {
|
||||
const auto creating = filter.title().isEmpty();
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit());
|
||||
box->setCloseByOutsideClick(false);
|
||||
|
||||
@@ -571,8 +572,9 @@ void EditFilterBox(
|
||||
|
||||
const auto includeAdd = AddButton(
|
||||
content,
|
||||
tr::lng_filters_add_chats() | Ui::Text::ToUpper(),
|
||||
st::settingsUpdate);
|
||||
tr::lng_filters_add_chats(),
|
||||
st::settingsButtonActive,
|
||||
{ &st::settingsIconAdd, 0, IconType::Round, &st::windowBgActive });
|
||||
|
||||
const auto include = SetupChatsPreview(
|
||||
content,
|
||||
@@ -582,21 +584,16 @@ void EditFilterBox(
|
||||
&Data::ChatFilter::always);
|
||||
|
||||
AddSkip(content);
|
||||
content->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
tr::lng_filters_include_about(),
|
||||
st::boxDividerLabel),
|
||||
st::windowFilterAboutPadding);
|
||||
AddDivider(content);
|
||||
AddDividerText(content, tr::lng_filters_include_about());
|
||||
AddSkip(content);
|
||||
|
||||
AddSubsectionTitle(content, tr::lng_filters_exclude());
|
||||
|
||||
const auto excludeAdd = AddButton(
|
||||
content,
|
||||
tr::lng_filters_remove_chats() | Ui::Text::ToUpper(),
|
||||
st::settingsUpdate);
|
||||
tr::lng_filters_remove_chats(),
|
||||
st::settingsButtonActive,
|
||||
{ &st::settingsIconRemove, 0, IconType::Round, &st::windowBgActive });
|
||||
|
||||
const auto exclude = SetupChatsPreview(
|
||||
content,
|
||||
@@ -606,12 +603,7 @@ void EditFilterBox(
|
||||
&Data::ChatFilter::never);
|
||||
|
||||
AddSkip(content);
|
||||
content->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
tr::lng_filters_exclude_about(),
|
||||
st::boxDividerLabel),
|
||||
st::windowFilterAboutPadding);
|
||||
AddDividerText(content, tr::lng_filters_exclude_about());
|
||||
|
||||
const auto refreshPreviews = [=] {
|
||||
include->updateData(
|
||||
|
||||
@@ -544,38 +544,36 @@ object_ptr<Ui::RpWidget> Controller::createStickersEdit() {
|
||||
Expects(_wrap != nullptr);
|
||||
|
||||
const auto channel = _peer->asChannel();
|
||||
const auto bottomSkip = st::editPeerTopButtonsLayoutSkipCustomBottom;
|
||||
|
||||
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
_wrap,
|
||||
object_ptr<Ui::VerticalLayout>(_wrap),
|
||||
st::editPeerInvitesMargins);
|
||||
object_ptr<Ui::VerticalLayout>(_wrap));
|
||||
const auto container = result->entity();
|
||||
|
||||
container->add(object_ptr<Ui::FlatLabel>(
|
||||
Settings::AddSubsectionTitle(
|
||||
container,
|
||||
tr::lng_group_stickers(),
|
||||
st::editPeerSectionLabel));
|
||||
container->add(object_ptr<Ui::FixedHeightWidget>(
|
||||
container,
|
||||
st::editPeerInviteLinkSkip));
|
||||
{ 0, st::settingsSubsectionTitlePadding.top() - bottomSkip, 0, 0 });
|
||||
|
||||
container->add(object_ptr<Ui::FlatLabel>(
|
||||
AddButtonWithCount(
|
||||
container,
|
||||
tr::lng_group_stickers_description(),
|
||||
st::editPeerPrivacyLabel));
|
||||
container->add(object_ptr<Ui::FixedHeightWidget>(
|
||||
container,
|
||||
st::editPeerInviteLinkSkip));
|
||||
tr::lng_group_stickers_add(),
|
||||
rpl::single(QString()), //Empty count.
|
||||
[=, controller = _navigation->parentController()] {
|
||||
controller->show(
|
||||
Box<StickersBox>(controller, channel),
|
||||
Ui::LayerOption::KeepOther);
|
||||
},
|
||||
{ &st::settingsIconStickers, Settings::kIconLightOrange });
|
||||
|
||||
container->add(object_ptr<Ui::LinkButton>(
|
||||
_wrap,
|
||||
tr::lng_group_stickers_add(tr::now),
|
||||
st::editPeerInviteLinkButton)
|
||||
)->addClickHandler([=] {
|
||||
_navigation->parentController()->show(
|
||||
Box<StickersBox>(_navigation->parentController(), channel),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
Settings::AddSkip(container, bottomSkip);
|
||||
|
||||
Settings::AddDividerText(
|
||||
container,
|
||||
tr::lng_group_stickers_description());
|
||||
|
||||
Settings::AddSkip(container, bottomSkip);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -707,6 +705,9 @@ void Controller::fillPrivacyTypeButton() {
|
||||
_noForwardsSavedValue = !_peer->allowsForwarding();
|
||||
|
||||
const auto isGroup = (_peer->isChat() || _peer->isMegagroup());
|
||||
const auto icon = isGroup
|
||||
? &st::settingsIconGroup
|
||||
: &st::settingsIconChannel;
|
||||
AddButtonWithText(
|
||||
_controls.buttonsLayout,
|
||||
(hasLocation
|
||||
@@ -729,7 +730,7 @@ void Controller::fillPrivacyTypeButton() {
|
||||
: tr::lng_manage_private_peer_title)();
|
||||
}) | rpl::flatten_latest(),
|
||||
[=] { showEditPeerTypeBox(); },
|
||||
{ &st::infoIconGroupType, Settings::kIconLightBlue });
|
||||
{ icon, Settings::kIconLightBlue });
|
||||
|
||||
_privacyTypeUpdates.fire_copy(*_privacySavedValue);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "info/profile/info_profile_icon.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_info.h"
|
||||
@@ -29,6 +28,7 @@ void EditAllowedReactionsBox(
|
||||
const std::vector<Data::Reaction> &list,
|
||||
const base::flat_set<QString> &selected,
|
||||
Fn<void(const std::vector<QString> &)> callback) {
|
||||
const auto iconHeight = st::editPeerReactionsPreview;
|
||||
box->setTitle(tr::lng_manage_peer_reactions());
|
||||
|
||||
struct State {
|
||||
@@ -57,10 +57,21 @@ void EditAllowedReactionsBox(
|
||||
container,
|
||||
tr::lng_manage_peer_reactions_enable(),
|
||||
st::manageGroupButton.button);
|
||||
Ui::CreateChild<Info::Profile::FloatingIcon>(
|
||||
enabled.get(),
|
||||
st::infoIconReactions,
|
||||
st::manageGroupButton.iconPosition);
|
||||
if (!list.empty()) {
|
||||
AddReactionLottieIcon(
|
||||
enabled,
|
||||
enabled->sizeValue(
|
||||
) | rpl::map([=](const QSize &size) {
|
||||
return QPoint(
|
||||
st::manageGroupButton.iconPosition.x(),
|
||||
(size.height() - iconHeight) / 2);
|
||||
}),
|
||||
iconHeight,
|
||||
list.front(),
|
||||
rpl::never<>(),
|
||||
rpl::never<>(),
|
||||
&enabled->lifetime());
|
||||
}
|
||||
enabled->toggleOn(state->anyToggled.value());
|
||||
enabled->toggledChanges(
|
||||
) | rpl::filter([=](bool value) {
|
||||
@@ -87,7 +98,6 @@ void EditAllowedReactionsBox(
|
||||
container,
|
||||
rpl::single(entry.title),
|
||||
st::manageGroupButton.button);
|
||||
const auto iconHeight = st::editPeerReactionsPreview;
|
||||
AddReactionLottieIcon(
|
||||
button,
|
||||
button->sizeValue(
|
||||
@@ -138,7 +148,7 @@ void SaveAllowedReactions(
|
||||
const std::vector<QString> &allowed) {
|
||||
auto ids = allowed | ranges::views::transform([=](QString value) {
|
||||
return MTP_string(value);
|
||||
}) | ranges::to<QVector>;
|
||||
}) | ranges::to<QVector<MTPstring>>;
|
||||
|
||||
peer->session().api().request(MTPmessages_SetChatAvailableReactions(
|
||||
peer->input,
|
||||
|
||||
@@ -89,12 +89,13 @@ AdminLog::OwnedItem GenerateItem(
|
||||
}
|
||||
|
||||
void AddMessage(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Window::SessionController*> controller,
|
||||
rpl::producer<QString> &&emojiValue) {
|
||||
rpl::producer<QString> &&emojiValue,
|
||||
int width) {
|
||||
|
||||
const auto widget = box->addRow(
|
||||
object_ptr<Ui::RpWidget>(box),
|
||||
const auto widget = container->add(
|
||||
object_ptr<Ui::RpWidget>(container),
|
||||
style::margins(
|
||||
0,
|
||||
st::settingsSectionSkip,
|
||||
@@ -121,7 +122,7 @@ void AddMessage(
|
||||
bool flag = false;
|
||||
} icons;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
const auto state = container->lifetime().make_state<State>();
|
||||
state->delegate = std::make_unique<Delegate>(
|
||||
controller,
|
||||
crl::guard(widget, [=] { widget->update(); }));
|
||||
@@ -150,16 +151,18 @@ void AddMessage(
|
||||
|
||||
const auto padding = st::settingsForwardPrivacyPadding;
|
||||
|
||||
widget->widthValue(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 >= (st::historyMinimalWidth / 2)
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto updateWidgetSize = [=](int width) {
|
||||
const auto height = view->resizeGetHeight(width);
|
||||
const auto top = view->marginTop();
|
||||
const auto bottom = view->marginBottom();
|
||||
const auto full = padding + top + height + bottom + padding;
|
||||
widget->resize(width, full);
|
||||
}, widget->lifetime());
|
||||
};
|
||||
widget->widthValue(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 >= (st::historyMinimalWidth / 2)
|
||||
) | rpl::start_with_next(updateWidgetSize, widget->lifetime());
|
||||
updateWidgetSize(width);
|
||||
|
||||
const auto rightSize = st::settingsReactionCornerSize;
|
||||
const auto rightRect = [=] {
|
||||
@@ -224,7 +227,7 @@ void AddMessage(
|
||||
const auto index = state->icons.flag ? 1 : 0;
|
||||
state->icons.lifetimes[index] = rpl::lifetime();
|
||||
AddReactionLottieIcon(
|
||||
box->verticalLayout(),
|
||||
container,
|
||||
widget->geometryValue(
|
||||
) | rpl::map([=](const QRect &r) {
|
||||
return widget->pos()
|
||||
@@ -388,34 +391,19 @@ void ReactionsSettingsBox(
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
state->selectedEmoji = reactions.favorite();
|
||||
|
||||
AddMessage(box, controller, state->selectedEmoji.value());
|
||||
const auto pinnedToTop = box->setPinnedToTopContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
|
||||
auto emojiValue = state->selectedEmoji.value();
|
||||
AddMessage(pinnedToTop, controller, std::move(emojiValue), box->width());
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
Settings::AddSubsectionTitle(
|
||||
container,
|
||||
pinnedToTop,
|
||||
tr::lng_settings_chat_reactions_subtitle());
|
||||
|
||||
const auto &stButton = st::settingsButton;
|
||||
const auto scrollContainer = box->addRow(
|
||||
object_ptr<Ui::FixedHeightWidget>(
|
||||
box,
|
||||
kVisibleButtonsCount
|
||||
* (stButton.height
|
||||
+ stButton.padding.top()
|
||||
+ stButton.padding.bottom())),
|
||||
style::margins());
|
||||
const auto scroll = Ui::CreateChild<Ui::ScrollArea>(
|
||||
scrollContainer,
|
||||
st::boxScroll);
|
||||
const auto buttonsContainer = scroll->setOwnedWidget(
|
||||
object_ptr<Ui::VerticalLayout>(scroll));
|
||||
scrollContainer->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &s) {
|
||||
scroll->resize(s.width(), s.height());
|
||||
buttonsContainer->resizeToWidth(s.width());
|
||||
}, scroll->lifetime());
|
||||
const auto container = box->verticalLayout();
|
||||
|
||||
const auto check = Ui::CreateChild<Ui::RpWidget>(buttonsContainer.data());
|
||||
const auto check = Ui::CreateChild<Ui::RpWidget>(container.get());
|
||||
check->resize(st::settingsReactionCornerSize);
|
||||
check->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
check->paintRequest(
|
||||
@@ -432,9 +420,9 @@ void ReactionsSettingsBox(
|
||||
auto firstCheckedButton = (Ui::RpWidget*)(nullptr);
|
||||
for (const auto &r : reactions.list(Data::Reactions::Type::Active)) {
|
||||
const auto button = Settings::AddButton(
|
||||
buttonsContainer,
|
||||
container,
|
||||
rpl::single<QString>(base::duplicate(r.title)),
|
||||
stButton);
|
||||
st::settingsButton);
|
||||
|
||||
const auto iconSize = st::settingsReactionSize;
|
||||
AddReactionLottieIcon(
|
||||
@@ -472,11 +460,6 @@ void ReactionsSettingsBox(
|
||||
}
|
||||
check->raise();
|
||||
|
||||
Ui::SetupShadowsToScrollContent(
|
||||
scrollContainer,
|
||||
scroll,
|
||||
buttonsContainer->heightValue());
|
||||
|
||||
box->setTitle(tr::lng_settings_chat_reactions_title());
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
|
||||
@@ -39,7 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -276,7 +276,7 @@ void RingtonesBox(
|
||||
tr::lng_ringtones_box_upload_button(),
|
||||
st::ringtonesBoxButton,
|
||||
{
|
||||
&st::mainMenuAddAccount,
|
||||
&st::settingsIconAdd,
|
||||
0,
|
||||
Settings::IconType::Round,
|
||||
&st::windowBgActive
|
||||
|
||||
@@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace tgcalls {
|
||||
class InstanceImpl;
|
||||
class InstanceV2Impl;
|
||||
class InstanceV2ReferenceImpl;
|
||||
class InstanceV2_4_0_0Impl;
|
||||
class InstanceImplLegacy;
|
||||
void SetLegacyGlobalServerConfig(const std::string &serverConfig);
|
||||
@@ -53,6 +54,7 @@ const auto kDefaultVersion = "2.4.4"_q;
|
||||
|
||||
const auto Register = tgcalls::Register<tgcalls::InstanceImpl>();
|
||||
const auto RegisterV2 = tgcalls::Register<tgcalls::InstanceV2Impl>();
|
||||
const auto RegV2Ref = tgcalls::Register<tgcalls::InstanceV2ReferenceImpl>();
|
||||
const auto RegisterV240 = tgcalls::Register<tgcalls::InstanceV2_4_0_0Impl>();
|
||||
const auto RegisterLegacy = tgcalls::Register<tgcalls::InstanceImplLegacy>();
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_instance.h"
|
||||
#include "inline_bots/bot_attach_web_view.h"
|
||||
#include "mainwidget.h"
|
||||
#include "tray.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "core/click_handler_types.h" // ClickHandlerContext.
|
||||
#include "core/crash_reports.h"
|
||||
@@ -149,6 +150,7 @@ Application::Application(not_null<Launcher*> launcher)
|
||||
, _langpack(std::make_unique<Lang::Instance>())
|
||||
, _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
|
||||
, _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
|
||||
, _tray(std::make_unique<Tray>())
|
||||
, _autoLockTimer([=] { checkAutoLock(); }) {
|
||||
Ui::Integration::Set(&_private->uiIntegration);
|
||||
|
||||
@@ -300,6 +302,8 @@ void Application::run() {
|
||||
startShortcuts();
|
||||
startDomain();
|
||||
|
||||
startTray();
|
||||
|
||||
_primaryWindow->widget()->show();
|
||||
|
||||
const auto currentGeometry = _primaryWindow->widget()->geometry();
|
||||
@@ -409,6 +413,38 @@ void Application::startSystemDarkModeViewer() {
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Application::startTray() {
|
||||
using WindowRaw = not_null<Window::Controller*>;
|
||||
const auto enumerate = [=](Fn<void(WindowRaw)> c) {
|
||||
if (_primaryWindow) {
|
||||
c(_primaryWindow.get());
|
||||
}
|
||||
for (const auto &window : ranges::views::values(_secondaryWindows)) {
|
||||
c(window.get());
|
||||
}
|
||||
};
|
||||
_tray->create();
|
||||
_tray->aboutToShowRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
enumerate([&](WindowRaw w) { w->updateIsActive(); });
|
||||
_tray->updateMenuText();
|
||||
}, _primaryWindow->widget()->lifetime());
|
||||
|
||||
_tray->showFromTrayRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto last = _lastActiveWindow;
|
||||
enumerate([&](WindowRaw w) { w->widget()->showFromTray(); });
|
||||
if (last) {
|
||||
last->widget()->showFromTray();
|
||||
}
|
||||
}, _primaryWindow->widget()->lifetime());
|
||||
|
||||
_tray->hideToTrayRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
enumerate([&](WindowRaw w) { w->widget()->minimizeToTray(); });
|
||||
}, _primaryWindow->widget()->lifetime());
|
||||
}
|
||||
|
||||
auto Application::prepareEmojiSourceImages()
|
||||
-> std::shared_ptr<Ui::Emoji::UniversalImages> {
|
||||
const auto &images = Ui::Emoji::SourceImages();
|
||||
@@ -427,6 +463,16 @@ void Application::clearEmojiSourceImages() {
|
||||
});
|
||||
}
|
||||
|
||||
bool Application::isActiveForTrayMenu() const {
|
||||
if (_primaryWindow) {
|
||||
return _primaryWindow->widget()->isActiveForTrayMenu();
|
||||
}
|
||||
return ranges::any_of(ranges::views::values(_secondaryWindows), [=](
|
||||
const std::unique_ptr<Window::Controller> &controller) {
|
||||
return controller->widget()->isActiveForTrayMenu();
|
||||
});
|
||||
}
|
||||
|
||||
bool Application::hideMediaView() {
|
||||
if (_mediaView && !_mediaView->isHidden()) {
|
||||
_mediaView->hide();
|
||||
|
||||
@@ -103,6 +103,7 @@ namespace Core {
|
||||
|
||||
class Launcher;
|
||||
struct LocalUrlHandler;
|
||||
class Tray;
|
||||
|
||||
enum class LaunchState {
|
||||
Running,
|
||||
@@ -147,6 +148,9 @@ public:
|
||||
[[nodiscard]] Data::DownloadManager &downloadManager() const {
|
||||
return *_downloadManager;
|
||||
}
|
||||
[[nodiscard]] Tray &tray() const {
|
||||
return *_tray;
|
||||
}
|
||||
|
||||
// Windows interface.
|
||||
bool hasActiveWindow(not_null<Main::Session*> session) const;
|
||||
@@ -163,6 +167,7 @@ public:
|
||||
[[nodiscard]] QWidget *getFileDialogParent();
|
||||
void notifyFileDialogShown(bool shown);
|
||||
void checkSystemDarkMode();
|
||||
[[nodiscard]] bool isActiveForTrayMenu() const;
|
||||
|
||||
// Media view interface.
|
||||
void checkMediaViewActivation();
|
||||
@@ -316,6 +321,7 @@ private:
|
||||
void startDomain();
|
||||
void startEmojiImageLoader();
|
||||
void startSystemDarkModeViewer();
|
||||
void startTray();
|
||||
|
||||
friend void QuitAttempt();
|
||||
void quitDelayed();
|
||||
@@ -376,6 +382,8 @@ private:
|
||||
std::unique_ptr<Lang::Translator> _translator;
|
||||
QPointer<Ui::BoxContent> _badProxyDisableBox;
|
||||
|
||||
const std::unique_ptr<Tray> _tray;
|
||||
|
||||
std::unique_ptr<Media::Player::FloatController> _floatPlayers;
|
||||
Media::Player::FloatDelegate *_defaultFloatPlayerDelegate = nullptr;
|
||||
Media::Player::FloatDelegate *_replacementFloatPlayerDelegate = nullptr;
|
||||
|
||||
@@ -121,6 +121,14 @@ std::map<int, const char*> BetaLogs() {
|
||||
"- Fix group and channel photo upload.\n"
|
||||
|
||||
"- Test hardware video decoding.\n"
|
||||
},
|
||||
{
|
||||
3007004,
|
||||
"- More icons for chat folders.\n"
|
||||
|
||||
"- Improve some more sections design.\n"
|
||||
|
||||
"- Update the OpenAL library to 1.22.0.\n"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -44,17 +44,9 @@ PreLaunchWindow::PreLaunchWindow(QString title) {
|
||||
p.setColor(QPalette::Window, QColor(255, 255, 255));
|
||||
setPalette(p);
|
||||
|
||||
constexpr auto processDpi = [](const QDpi &dpi) {
|
||||
return (dpi.first + dpi.second) * 0.5;
|
||||
};
|
||||
|
||||
const auto screen = QGuiApplication::primaryScreen();
|
||||
const auto scale = processDpi(screen->handle()->logicalDpi())
|
||||
/ processDpi(screen->handle()->logicalBaseDpi());
|
||||
|
||||
auto font = QGuiApplication::font();
|
||||
font.setPixelSize(base::SafeRound(font.pointSize() * scale));
|
||||
|
||||
const auto dpi = screen()->handle()->logicalDpi().second;
|
||||
auto font = this->font();
|
||||
font.setPixelSize(base::SafeRound(std::floor(font.pointSizeF() * dpi / 72. * 100. + 0.5) / 100.));
|
||||
_size = QFontMetrics(font).height();
|
||||
|
||||
int paddingVertical = (_size / 2);
|
||||
@@ -110,12 +102,12 @@ PreLaunchInput::PreLaunchInput(QWidget *parent, bool password) : QLineEdit(paren
|
||||
setFont(logFont);
|
||||
|
||||
QPalette p(palette());
|
||||
p.setColor(QPalette::Window, QColor(255, 255, 255));
|
||||
p.setColor(QPalette::Base, QColor(255, 255, 255));
|
||||
p.setColor(QPalette::WindowText, QColor(0, 0, 0));
|
||||
p.setColor(QPalette::Text, QColor(0, 0, 0));
|
||||
setPalette(p);
|
||||
|
||||
setStyleSheet("QLineEdit { background-color: white; }");
|
||||
|
||||
QLineEdit::setTextMargins(0, 0, 0, 0);
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
if (password) {
|
||||
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "boxes/sessions_box.h"
|
||||
#include "boxes/language_box.h"
|
||||
#include "boxes/change_phone_box.h"
|
||||
#include "passport/passport_form_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "ui/toast/toast.h"
|
||||
@@ -40,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "settings/settings_common.h"
|
||||
#include "settings/settings_folders.h"
|
||||
#include "settings/settings_main.h"
|
||||
#include "settings/settings_privacy_security.h"
|
||||
#include "settings/settings_chat.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
@@ -444,22 +447,30 @@ bool ResolveSettings(
|
||||
}
|
||||
controller->window().activate();
|
||||
const auto section = match->captured(1).mid(1).toLower();
|
||||
if (section.isEmpty()) {
|
||||
controller->window().showSettings();
|
||||
return true;
|
||||
} else if (section == qstr("language")) {
|
||||
ShowLanguagesBox();
|
||||
return true;
|
||||
} else if (section == qstr("devices")) {
|
||||
controller->session().api().authorizations().reload();
|
||||
|
||||
const auto type = [&]() -> std::optional<::Settings::Type> {
|
||||
if (section == qstr("language")) {
|
||||
ShowLanguagesBox();
|
||||
return {};
|
||||
} else if (section == qstr("devices")) {
|
||||
controller->session().api().authorizations().reload();
|
||||
return ::Settings::Sessions::Id();
|
||||
} else if (section == qstr("folders")) {
|
||||
return ::Settings::Folders::Id();
|
||||
} else if (section == qstr("privacy")) {
|
||||
return ::Settings::PrivacySecurity::Id();
|
||||
} else if (section == qstr("themes")) {
|
||||
return ::Settings::Chat::Id();
|
||||
} else if (section == qstr("change_number")) {
|
||||
return ::Settings::ChangePhone::Id();
|
||||
}
|
||||
return ::Settings::Main::Id();
|
||||
}();
|
||||
|
||||
if (type.has_value()) {
|
||||
controller->showSettings(*type);
|
||||
controller->window().activate();
|
||||
}
|
||||
const auto type = (section == qstr("folders"))
|
||||
? ::Settings::Folders::Id()
|
||||
: (section == qstr("devices"))
|
||||
? ::Settings::Sessions::Id()
|
||||
: ::Settings::Main::Id();
|
||||
controller->showSettings(type);
|
||||
controller->window().activate();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -760,7 +771,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
ResolvePrivatePost
|
||||
},
|
||||
{
|
||||
qsl("^settings(/folders|/devices|/language)?$"),
|
||||
qsl("^settings(/language|/devices|/folders|/privacy|/themes|/change_number)?$"),
|
||||
ResolveSettings
|
||||
},
|
||||
{
|
||||
|
||||
@@ -324,6 +324,14 @@ QString UiIntegration::phraseFormattingSpoiler() {
|
||||
return tr::lng_menu_formatting_spoiler(tr::now);
|
||||
}
|
||||
|
||||
QString UiIntegration::phraseButtonOk() {
|
||||
return tr::lng_box_ok(tr::now);
|
||||
}
|
||||
|
||||
QString UiIntegration::phraseButtonCancel() {
|
||||
return tr::lng_cancel(tr::now);
|
||||
}
|
||||
|
||||
bool OpenGLLastCheckFailed() {
|
||||
return QFile::exists(OpenGLCheckFilePath());
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ public:
|
||||
QString phraseFormattingStrikeOut() override;
|
||||
QString phraseFormattingMonospace() override;
|
||||
QString phraseFormattingSpoiler() override;
|
||||
QString phraseButtonOk() override;
|
||||
QString phraseButtonCancel() override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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 = 3007002;
|
||||
constexpr auto AppVersionStr = "3.7.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppVersion = 3007004;
|
||||
constexpr auto AppVersionStr = "3.7.4";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -161,25 +161,7 @@ void NotifySettings::updateLocal(not_null<PeerData*> peer) {
|
||||
} else {
|
||||
_mutedPeers.erase(peer);
|
||||
}
|
||||
|
||||
if (const auto sound = peer->notifySound(); sound && sound->id) {
|
||||
if (const auto doc = _owner->document(sound->id); !doc->isNull()) {
|
||||
cacheSound(doc);
|
||||
} else {
|
||||
_ringtones.pendingIds.push_back(sound->id);
|
||||
if (!_ringtones.pendingLifetime) {
|
||||
// Not requested yet.
|
||||
_owner->session().api().ringtones().listUpdates(
|
||||
) | rpl::start_with_next([=] {
|
||||
for (const auto id : base::take(_ringtones.pendingIds)) {
|
||||
cacheSound(id);
|
||||
}
|
||||
_ringtones.pendingLifetime.destroy();
|
||||
}, _ringtones.pendingLifetime);
|
||||
_owner->session().api().ringtones().requestList();
|
||||
}
|
||||
}
|
||||
}
|
||||
cacheSound(peer->notifySound());
|
||||
}
|
||||
|
||||
void NotifySettings::cacheSound(DocumentId id) {
|
||||
@@ -196,6 +178,28 @@ void NotifySettings::cacheSound(not_null<DocumentData*> document) {
|
||||
document->save(Data::FileOriginRingtones(), QString());
|
||||
}
|
||||
|
||||
void NotifySettings::cacheSound(const std::optional<NotifySound> &sound) {
|
||||
if (!sound || !sound->id) {
|
||||
return;
|
||||
} else if (const auto doc = _owner->document(sound->id); !doc->isNull()) {
|
||||
cacheSound(doc);
|
||||
return;
|
||||
}
|
||||
_ringtones.pendingIds.push_back(sound->id);
|
||||
if (_ringtones.pendingLifetime) {
|
||||
return;
|
||||
}
|
||||
// Not requested yet.
|
||||
_owner->session().api().ringtones().listUpdates(
|
||||
) | rpl::start_with_next([=] {
|
||||
for (const auto id : base::take(_ringtones.pendingIds)) {
|
||||
cacheSound(id);
|
||||
}
|
||||
_ringtones.pendingLifetime.destroy();
|
||||
}, _ringtones.pendingLifetime);
|
||||
_owner->session().api().ringtones().requestList();
|
||||
}
|
||||
|
||||
void NotifySettings::updateLocal(DefaultNotify type) {
|
||||
defaultValue(type).updates.fire({});
|
||||
|
||||
@@ -220,6 +224,7 @@ void NotifySettings::updateLocal(DefaultNotify type) {
|
||||
_owner->enumerateBroadcasts(callback);
|
||||
break;
|
||||
}
|
||||
cacheSound(defaultValue(type).settings.sound());
|
||||
}
|
||||
|
||||
std::shared_ptr<DocumentMedia> NotifySettings::lookupRingtone(
|
||||
|
||||
@@ -71,6 +71,8 @@ private:
|
||||
rpl::event_stream<> updates;
|
||||
};
|
||||
|
||||
void cacheSound(const std::optional<NotifySound> &sound);
|
||||
|
||||
[[nodiscard]] bool isMuted(
|
||||
not_null<const PeerData*> peer,
|
||||
crl::time *changesIn) const;
|
||||
|
||||
@@ -27,11 +27,6 @@ namespace {
|
||||
constexpr auto kMaxBrush = 25.;
|
||||
constexpr auto kMinBrush = 1.;
|
||||
|
||||
constexpr auto kViewStyle = "QGraphicsView {\
|
||||
background-color: transparent;\
|
||||
border: 0px\
|
||||
}"_cs;
|
||||
|
||||
std::shared_ptr<Scene> EnsureScene(
|
||||
PhotoModifications &mods,
|
||||
const QSize &size) {
|
||||
@@ -62,7 +57,8 @@ Paint::Paint(
|
||||
_view->show();
|
||||
_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
_view->setStyleSheet(kViewStyle.utf8());
|
||||
_view->setFrameStyle(int(QFrame::NoFrame) | QFrame::Plain);
|
||||
_view->viewport()->setAutoFillBackground(false);
|
||||
|
||||
// Undo / Redo.
|
||||
controllers->undoController->performRequestChanges(
|
||||
|
||||
@@ -1243,6 +1243,11 @@ bool ListWidget::hasCopyRestrictionForSelected() const {
|
||||
if (hasCopyRestriction()) {
|
||||
return true;
|
||||
}
|
||||
if (_selected.empty()) {
|
||||
if (_selectedTextItem && _selectedTextItem->forbidsForward()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const auto &[itemId, selection] : _selected) {
|
||||
if (const auto item = session().data().message(itemId)) {
|
||||
if (item->forbidsForward()) {
|
||||
@@ -1254,6 +1259,11 @@ bool ListWidget::hasCopyRestrictionForSelected() const {
|
||||
}
|
||||
|
||||
bool ListWidget::showCopyRestrictionForSelected() {
|
||||
if (_selected.empty()) {
|
||||
if (_selectedTextItem && showCopyRestriction(_selectedTextItem)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const auto &[itemId, selection] : _selected) {
|
||||
if (showCopyRestriction(session().data().message(itemId))) {
|
||||
return true;
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_pinned_tracker.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
@@ -163,27 +164,32 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
||||
consumer.put_next(nullptr);
|
||||
|
||||
struct State {
|
||||
HistoryMessageReplyMarkup *previousReplyMarkup = nullptr;
|
||||
bool hasReplyMarkup = false;
|
||||
base::has_weak_ptr guard;
|
||||
rpl::lifetime lifetime;
|
||||
FullMsgId resolvedId;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>();
|
||||
|
||||
const auto pushUnique = [=](not_null<HistoryItem*> item) {
|
||||
const auto replyMarkup = item->inlineReplyMarkup();
|
||||
if (state->previousReplyMarkup == replyMarkup) {
|
||||
if (!state->hasReplyMarkup && !replyMarkup) {
|
||||
return;
|
||||
}
|
||||
state->hasReplyMarkup = (replyMarkup != nullptr);
|
||||
consumer.put_next(item.get());
|
||||
state->previousReplyMarkup = replyMarkup;
|
||||
};
|
||||
|
||||
rpl::duplicate(
|
||||
id
|
||||
) | rpl::start_with_next([=](PinnedId current) {
|
||||
) | rpl::filter([=](PinnedId current) {
|
||||
return current.message && (current.message != state->resolvedId);
|
||||
}) | rpl::start_with_next([=](PinnedId current) {
|
||||
const auto fullId = current.message;
|
||||
if (!fullId) {
|
||||
return;
|
||||
}
|
||||
state->lifetime.destroy();
|
||||
state->resolvedId = fullId;
|
||||
invalidate_weak_ptrs(&state->guard);
|
||||
|
||||
const auto messageFlag = [=](not_null<HistoryItem*> item) {
|
||||
using Update = Data::MessageUpdate;
|
||||
session->changes().messageFlagsValue(
|
||||
@@ -195,12 +201,17 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
||||
};
|
||||
if (const auto item = session->data().message(fullId)) {
|
||||
messageFlag(item);
|
||||
} else {
|
||||
session->api().requestMessageData(
|
||||
session->data().peer(fullId.peer),
|
||||
fullId.msg,
|
||||
[=] { messageFlag(session->data().message(fullId)); });
|
||||
return;
|
||||
}
|
||||
const auto resolved = crl::guard(&state->guard, [=] {
|
||||
if (const auto item = session->data().message(fullId)) {
|
||||
messageFlag(item);
|
||||
}
|
||||
});
|
||||
session->api().requestMessageData(
|
||||
session->data().peer(fullId.peer),
|
||||
fullId.msg,
|
||||
resolved);
|
||||
}, lifetime);
|
||||
return lifetime;
|
||||
});
|
||||
|
||||
@@ -316,7 +316,7 @@ infoProfileSeparatorPadding: margins(
|
||||
|
||||
infoIconFg: windowBoldFg;
|
||||
infoIconInformation: icon {{ "info/info_information", infoIconFg }};
|
||||
infoIconRequests: icon {{ "info/info_add_member", infoIconFg }};
|
||||
infoIconRequests: icon {{ "info/edit/group_manage_join_requests", infoIconFg }};
|
||||
infoIconNotifications: icon {{ "info/info_notifications", infoIconFg }};
|
||||
infoIconMediaPhoto: icon {{ "info/info_media_photo", infoIconFg }};
|
||||
infoIconMediaVideo: icon {{ "info/info_media_video", infoIconFg }};
|
||||
@@ -331,7 +331,6 @@ infoIconRecentActions: icon {{ "info/edit/group_manage_actions", settingsIconFg
|
||||
infoIconAdministrators: icon {{ "info/edit/group_manage_admins", settingsIconFg }};
|
||||
infoIconInviteLinks: icon {{ "info/edit/group_manage_links", settingsIconFg }};
|
||||
infoIconReactions: icon {{ "info/edit/group_manage_reactions", settingsIconFg }};
|
||||
infoIconGroupType: icon {{ "info/edit/group_manage_type", settingsIconFg }};
|
||||
infoIconSignature: icon {{ "info/edit/channel_manage_signature", settingsIconFg }};
|
||||
infoIconShare: icon {{ "info/info_share", infoIconFg }};
|
||||
infoIconEdit: icon {{ "info/info_edit", infoIconFg }};
|
||||
@@ -622,13 +621,6 @@ editPeerPrivacyBoxCheckbox: Checkbox(defaultBoxCheckbox) {
|
||||
editPeerHistoryVisibilityLabelMargins: margins(34px, 0px, 48px, 0px);
|
||||
editPeerPrivacyLabelMargins: margins(42px, 0px, 34px, 0px);
|
||||
editPeerPreHistoryLabelMargins: margins(34px, 0px, 34px, 0px);
|
||||
editPeerSectionLabel: FlatLabel(boxTitle) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(15px semibold);
|
||||
linkFont: font(15px semibold);
|
||||
linkFontOver: font(15px semibold underline);
|
||||
}
|
||||
}
|
||||
editPeerUsernameTitleLabelMargins: margins(22px, 17px, 22px, 10px);
|
||||
editPeerUsernameFieldMargins: margins(22px, 0px, 22px, 20px);
|
||||
editPeerUsername: setupChannelLink;
|
||||
@@ -637,7 +629,6 @@ editPeerInviteLink: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 1px; // for break everywhere
|
||||
style: boxTextStyle;
|
||||
}
|
||||
editPeerInviteLinkButton: boxLinkButton;
|
||||
editPeerUsernameGood: FlatLabel(defaultFlatLabel) {
|
||||
textFg: boxTextFgGood;
|
||||
style: boxTextStyle;
|
||||
@@ -646,11 +637,6 @@ editPeerUsernameError: FlatLabel(editPeerUsernameGood) {
|
||||
textFg: boxTextFgError;
|
||||
}
|
||||
editPeerUsernamePosition: point(22px, 18px);
|
||||
editPeerInviteLinkSkip: 10px;
|
||||
editPeerInvitesMargins: margins(22px, 17px, 22px, 16px);
|
||||
editPeerInvitesTopSkip: 10px;
|
||||
editPeerInvitesSkip: 10px;
|
||||
editPeerInviteLinkBoxBottomSkip: 15px;
|
||||
|
||||
editPeerReactionsButton: SettingsButton(infoProfileButton) {
|
||||
padding: margins(59px, 13px, 8px, 11px);
|
||||
@@ -752,36 +738,6 @@ topBarConnectingAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimatio
|
||||
|
||||
infoFeedLeaveIconMargins: margins(10px, 12px, 20px, 10px);
|
||||
|
||||
separatePanelBorderCacheSize: 60px;
|
||||
separatePanelTitleHeight: 62px;
|
||||
separatePanelClose: IconButton(boxTitleClose) {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
|
||||
rippleAreaPosition: point(8px, 8px);
|
||||
rippleAreaSize: 44px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
}
|
||||
separatePanelTitleFont: font(18px semibold);
|
||||
separatePanelTitle: FlatLabel(defaultFlatLabel) {
|
||||
textFg: boxTitleFg;
|
||||
maxHeight: 26px;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: separatePanelTitleFont;
|
||||
linkFont: separatePanelTitleFont;
|
||||
linkFontOver: font(18px semibold underline);
|
||||
}
|
||||
}
|
||||
separatePanelTitleTop: 18px;
|
||||
separatePanelTitleLeft: 22px;
|
||||
separatePanelTitleSkip: 0px;
|
||||
separatePanelBack: IconButton(separatePanelClose) {
|
||||
icon: infoTopBarBackIcon;
|
||||
iconOver: infoTopBarBackIconOver;
|
||||
}
|
||||
|
||||
inviteLinkField: FlatInput(defaultFlatInput) {
|
||||
font: font(fsize);
|
||||
|
||||
|
||||
@@ -82,7 +82,11 @@ void LayerWidget::setupHeightConsumers() {
|
||||
|
||||
_content->scrollTillBottomChanges(
|
||||
) | rpl::filter([this] {
|
||||
return !_inResize;
|
||||
if (!_inResize) {
|
||||
return true;
|
||||
}
|
||||
_pendingResize = true;
|
||||
return false;
|
||||
}) | rpl::start_with_next([this] {
|
||||
resizeToWidth(width());
|
||||
}, lifetime());
|
||||
@@ -125,8 +129,11 @@ void LayerWidget::setContentHeight(int height) {
|
||||
if (_contentHeight == height) {
|
||||
return;
|
||||
}
|
||||
|
||||
_contentHeight = height;
|
||||
if (_content && !_inResize) {
|
||||
if (_inResize) {
|
||||
_pendingResize = true;
|
||||
} else if (_content) {
|
||||
resizeToWidth(width());
|
||||
}
|
||||
}
|
||||
@@ -240,9 +247,29 @@ int LayerWidget::resizeGetHeight(int newWidth) {
|
||||
if (!parentWidget() || !_content) {
|
||||
return 0;
|
||||
}
|
||||
_inResize = true;
|
||||
auto guard = gsl::finally([&] { _inResize = false; });
|
||||
constexpr auto kMaxAttempts = 5;
|
||||
auto attempts = 0;
|
||||
while (true) {
|
||||
_inResize = true;
|
||||
const auto newGeometry = countGeometry(newWidth);
|
||||
_inResize = false;
|
||||
if (!_pendingResize) {
|
||||
const auto oldGeometry = geometry();
|
||||
if (newGeometry != oldGeometry) {
|
||||
_content->forceContentRepaint();
|
||||
}
|
||||
if (newGeometry.topLeft() != oldGeometry.topLeft()) {
|
||||
move(newGeometry.topLeft());
|
||||
}
|
||||
floatPlayerUpdatePositions();
|
||||
return newGeometry.height();
|
||||
}
|
||||
_pendingResize = false;
|
||||
Assert(attempts++ < kMaxAttempts);
|
||||
}
|
||||
}
|
||||
|
||||
QRect LayerWidget::countGeometry(int newWidth) {
|
||||
auto parentSize = parentWidget()->size();
|
||||
auto windowWidth = parentSize.width();
|
||||
auto windowHeight = parentSize.height();
|
||||
@@ -282,16 +309,7 @@ int LayerWidget::resizeGetHeight(int newWidth) {
|
||||
contentHeight,
|
||||
}, expanding, additionalScroll);
|
||||
|
||||
auto newGeometry = QRect(newLeft, newTop, newWidth, desiredHeight);
|
||||
if (newGeometry != geometry()) {
|
||||
_content->forceContentRepaint();
|
||||
}
|
||||
if (newGeometry.topLeft() != geometry().topLeft()) {
|
||||
move(newGeometry.topLeft());
|
||||
}
|
||||
|
||||
floatPlayerUpdatePositions();
|
||||
return desiredHeight;
|
||||
return QRect(newLeft, newTop, newWidth, desiredHeight);
|
||||
}
|
||||
|
||||
void LayerWidget::doSetInnerFocus() {
|
||||
|
||||
@@ -69,6 +69,7 @@ private:
|
||||
|
||||
void setupHeightConsumers();
|
||||
void setContentHeight(int height);
|
||||
[[nodiscard]] QRect countGeometry(int newWidth);
|
||||
|
||||
not_null<Window::SessionController*> _controller;
|
||||
object_ptr<WrapWidget> _content;
|
||||
@@ -80,6 +81,7 @@ private:
|
||||
Ui::Animations::Simple _savedHeightAnimation;
|
||||
bool _heightAnimated = false;
|
||||
bool _inResize = false;
|
||||
bool _pendingResize = false;
|
||||
bool _tillBottom = false;
|
||||
|
||||
bool _floatPlayerDelegateRestored = false;
|
||||
|
||||
@@ -47,11 +47,25 @@ Widget::Widget(
|
||||
, _type(controller->section().settingsType())
|
||||
, _inner(
|
||||
setInnerWidget(
|
||||
_type()->create(this, controller->parentController()))) {
|
||||
_type()->create(this, controller->parentController())))
|
||||
, _pinnedToTop(_inner->createPinnedToTop(this)) {
|
||||
_inner->sectionShowOther(
|
||||
) | rpl::start_with_next([=](Type type) {
|
||||
controller->showSettings(type);
|
||||
}, _inner->lifetime());
|
||||
|
||||
if (_pinnedToTop) {
|
||||
_inner->widthValue(
|
||||
) | rpl::start_with_next([=](int w) {
|
||||
_pinnedToTop->resizeToWidth(w);
|
||||
setScrollTopSkip(_pinnedToTop->height());
|
||||
}, _pinnedToTop->lifetime());
|
||||
|
||||
_pinnedToTop->heightValue(
|
||||
) | rpl::start_with_next([=](int h) {
|
||||
setScrollTopSkip(h);
|
||||
}, _pinnedToTop->lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
Widget::~Widget() = default;
|
||||
|
||||
@@ -81,6 +81,7 @@ private:
|
||||
Type _type = Type();
|
||||
|
||||
not_null<::Settings::AbstractSection*> _inner;
|
||||
QPointer<Ui::RpWidget> _pinnedToTop;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -123,55 +123,6 @@ void MainWindow::initHook() {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::createTrayIconMenu() {
|
||||
#ifdef Q_OS_WIN
|
||||
trayIconMenu = new Ui::PopupMenu(nullptr);
|
||||
trayIconMenu->deleteOnHide(false);
|
||||
#else // Q_OS_WIN
|
||||
trayIconMenu = new QMenu(this);
|
||||
|
||||
connect(trayIconMenu, &QMenu::aboutToShow, [=] {
|
||||
updateIsActive();
|
||||
updateTrayMenu();
|
||||
});
|
||||
#endif // else for Q_OS_WIN
|
||||
|
||||
const auto minimizeAction = trayIconMenu->addAction(QString(), [=] {
|
||||
if (_activeForTrayIconAction) {
|
||||
minimizeToTray();
|
||||
} else {
|
||||
showFromTrayMenu();
|
||||
}
|
||||
});
|
||||
const auto notificationAction = trayIconMenu->addAction(QString(), [=] {
|
||||
toggleDisplayNotifyFromTray();
|
||||
});
|
||||
trayIconMenu->addAction(tr::lng_quit_from_tray(tr::now), [=] {
|
||||
quitFromTray();
|
||||
});
|
||||
|
||||
_updateTrayMenuTextActions.events(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!trayIconMenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
_activeForTrayIconAction = isActiveForTrayMenu();
|
||||
minimizeAction->setText(_activeForTrayIconAction
|
||||
? tr::lng_minimize_to_tray(tr::now)
|
||||
: tr::lng_open_from_tray(tr::now));
|
||||
|
||||
auto notificationActionText = Core::App().settings().desktopNotify()
|
||||
? tr::lng_disable_notifications_from_tray(tr::now)
|
||||
: tr::lng_enable_notifications_from_tray(tr::now);
|
||||
notificationAction->setText(notificationActionText);
|
||||
}, lifetime());
|
||||
|
||||
_updateTrayMenuTextActions.fire({});
|
||||
|
||||
initTrayMenuHook();
|
||||
}
|
||||
|
||||
void MainWindow::applyInitialWorkMode() {
|
||||
const auto workMode = Core::App().settings().workMode();
|
||||
workmodeUpdated(workMode);
|
||||
@@ -196,7 +147,6 @@ void MainWindow::applyInitialWorkMode() {
|
||||
}
|
||||
|
||||
void MainWindow::finishFirstShow() {
|
||||
createTrayIconMenu();
|
||||
applyInitialWorkMode();
|
||||
createGlobalMenu();
|
||||
|
||||
@@ -680,15 +630,6 @@ bool MainWindow::eventFilter(QObject *object, QEvent *e) {
|
||||
return Platform::MainWindow::eventFilter(object, e);
|
||||
}
|
||||
|
||||
void MainWindow::updateTrayMenu() {
|
||||
if (!trayIconMenu) {
|
||||
return;
|
||||
}
|
||||
_updateTrayMenuTextActions.fire({});
|
||||
|
||||
psTrayMenuUpdated();
|
||||
}
|
||||
|
||||
bool MainWindow::takeThirdSectionFromLayer() {
|
||||
return _layer ? _layer->takeToThirdSection() : false;
|
||||
}
|
||||
@@ -700,91 +641,6 @@ void MainWindow::fixOrder() {
|
||||
if (_testingThemeWarning) _testingThemeWarning->raise();
|
||||
}
|
||||
|
||||
void MainWindow::handleTrayIconActication(
|
||||
QSystemTrayIcon::ActivationReason reason) {
|
||||
updateIsActive();
|
||||
if (Platform::IsMac() && isActive()) {
|
||||
if (trayIcon && !trayIcon->contextMenu()) {
|
||||
showFromTray();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (reason == QSystemTrayIcon::Context) {
|
||||
updateTrayMenu();
|
||||
InvokeQueued(this, [=] {
|
||||
psShowTrayMenu();
|
||||
});
|
||||
} else if (!skipTrayClick()) {
|
||||
if (isActiveForTrayMenu()) {
|
||||
minimizeToTray();
|
||||
} else {
|
||||
showFromTray();
|
||||
}
|
||||
_lastTrayClickTime = crl::now();
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::skipTrayClick() const {
|
||||
return (_lastTrayClickTime > 0)
|
||||
&& (crl::now() - _lastTrayClickTime
|
||||
< QApplication::doubleClickInterval());
|
||||
}
|
||||
|
||||
void MainWindow::toggleDisplayNotifyFromTray() {
|
||||
if (controller().locked()) {
|
||||
if (!isActive()) showFromTray();
|
||||
Ui::show(Ui::MakeInformBox(tr::lng_passcode_need_unblock()));
|
||||
return;
|
||||
}
|
||||
if (!sessionController()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto soundNotifyChanged = false;
|
||||
auto flashBounceNotifyChanged = false;
|
||||
auto &settings = Core::App().settings();
|
||||
settings.setDesktopNotify(!settings.desktopNotify());
|
||||
if (settings.desktopNotify()) {
|
||||
if (settings.rememberedSoundNotifyFromTray()
|
||||
&& !settings.soundNotify()) {
|
||||
settings.setSoundNotify(true);
|
||||
settings.setRememberedSoundNotifyFromTray(false);
|
||||
soundNotifyChanged = true;
|
||||
}
|
||||
if (settings.rememberedFlashBounceNotifyFromTray()
|
||||
&& !settings.flashBounceNotify()) {
|
||||
settings.setFlashBounceNotify(true);
|
||||
settings.setRememberedFlashBounceNotifyFromTray(false);
|
||||
flashBounceNotifyChanged = true;
|
||||
}
|
||||
} else {
|
||||
if (settings.soundNotify()) {
|
||||
settings.setSoundNotify(false);
|
||||
settings.setRememberedSoundNotifyFromTray(true);
|
||||
soundNotifyChanged = true;
|
||||
} else {
|
||||
settings.setRememberedSoundNotifyFromTray(false);
|
||||
}
|
||||
if (settings.flashBounceNotify()) {
|
||||
settings.setFlashBounceNotify(false);
|
||||
settings.setRememberedFlashBounceNotifyFromTray(true);
|
||||
flashBounceNotifyChanged = true;
|
||||
} else {
|
||||
settings.setRememberedFlashBounceNotifyFromTray(false);
|
||||
}
|
||||
}
|
||||
Core::App().saveSettingsDelayed();
|
||||
using Change = Window::Notifications::ChangeType;
|
||||
auto ¬ifications = Core::App().notifications();
|
||||
notifications.notifySettingsChanged(Change::DesktopEnabled);
|
||||
if (soundNotifyChanged) {
|
||||
notifications.notifySettingsChanged(Change::SoundEnabled);
|
||||
}
|
||||
if (flashBounceNotifyChanged) {
|
||||
notifications.notifySettingsChanged(Change::FlashBounceEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent *e) {
|
||||
if (Core::Sandbox::Instance().isSavingSession() || Core::Quitting()) {
|
||||
e->accept();
|
||||
@@ -853,10 +709,7 @@ void MainWindow::activeChangedHook() {
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
delete trayIcon;
|
||||
delete trayIconMenu;
|
||||
}
|
||||
MainWindow::~MainWindow() = default;
|
||||
|
||||
namespace App {
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ public:
|
||||
}
|
||||
|
||||
void showMainMenu();
|
||||
void updateTrayMenu() override;
|
||||
void fixOrder() override;
|
||||
|
||||
void showLayer(
|
||||
@@ -119,12 +118,6 @@ protected:
|
||||
void clearWidgetsHook() override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool skipTrayClick() const;
|
||||
|
||||
void createTrayIconMenu();
|
||||
void handleTrayIconActication(
|
||||
QSystemTrayIcon::ActivationReason reason) override;
|
||||
|
||||
void applyInitialWorkMode();
|
||||
void ensureLayerCreated();
|
||||
void destroyLayer();
|
||||
@@ -139,15 +132,11 @@ private:
|
||||
|
||||
void themeUpdated(const Window::Theme::BackgroundUpdate &data);
|
||||
|
||||
void toggleDisplayNotifyFromTray();
|
||||
|
||||
QPixmap grabInner();
|
||||
|
||||
std::unique_ptr<Media::SystemMediaControlsManager> _mediaControlsManager;
|
||||
|
||||
crl::time _lastTrayClickTime = 0;
|
||||
QPoint _lastMousePosition;
|
||||
bool _activeForTrayIconAction = true;
|
||||
|
||||
object_ptr<Window::PasscodeLockWidget> _passcodeLock = { nullptr };
|
||||
object_ptr<Intro::Widget> _intro = { nullptr };
|
||||
@@ -157,8 +146,6 @@ private:
|
||||
|
||||
object_ptr<Window::Theme::WarningWidget> _testingThemeWarning = { nullptr };
|
||||
|
||||
rpl::event_stream<> _updateTrayMenuTextActions;
|
||||
|
||||
};
|
||||
|
||||
namespace App {
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "media/audio/media_audio_track.h"
|
||||
#include "media/audio/media_openal_functions.h"
|
||||
#include "media/streaming/media_streaming_utility.h"
|
||||
#include "webrtc/webrtc_media_devices.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -80,6 +81,10 @@ bool PlaybackErrorHappened() {
|
||||
}
|
||||
|
||||
void EnumeratePlaybackDevices() {
|
||||
if (!Webrtc::InitPipewireStubs()) {
|
||||
LOG(("Audio Info: Failed to load pipewire 0.3 stubs."));
|
||||
}
|
||||
|
||||
auto deviceNames = QStringList();
|
||||
auto devices = [&] {
|
||||
if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) {
|
||||
|
||||
@@ -537,8 +537,6 @@ void OverlayWidget::updateGeometry(bool inMove) {
|
||||
.arg(use.width())
|
||||
.arg(use.height()));
|
||||
_widget->setGeometry(use);
|
||||
_widget->setMinimumSize(use.size());
|
||||
_widget->setMaximumSize(use.size());
|
||||
if (possibleSizeHack) {
|
||||
_widget->setMask(mask);
|
||||
}
|
||||
|
||||
@@ -108,9 +108,11 @@ bool TTLValidator::can() const {
|
||||
&& !_peer->isNotificationsUser()
|
||||
&& !_peer->asUser()->isInaccessible())
|
||||
|| (_peer->isChat()
|
||||
&& _peer->asChat()->canDeleteMessages())
|
||||
&& _peer->asChat()->canDeleteMessages()
|
||||
&& _peer->asChat()->amIn())
|
||||
|| (_peer->isChannel()
|
||||
&& _peer->asChannel()->canDeleteMessages());
|
||||
&& _peer->asChannel()->canDeleteMessages()
|
||||
&& _peer->asChannel()->amIn());
|
||||
}
|
||||
|
||||
void TTLValidator::showToast() const {
|
||||
|
||||
@@ -65,7 +65,7 @@ QByteArray DnsUserAgent() {
|
||||
static const auto kResult = QByteArray(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/100.0.4896.60 Safari/537.36");
|
||||
"Chrome/100.0.4896.127 Safari/537.36");
|
||||
return kResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,8 +52,9 @@ Launcher::Launcher(int argc, char *argv[])
|
||||
|
||||
int Launcher::exec() {
|
||||
for (auto i = begin(_arguments), e = end(_arguments); i != e; ++i) {
|
||||
if (*i == "-webviewhelper" && std::distance(i, e) > 1) {
|
||||
Webview::WebKit2Gtk::SetSocketPath(*(i + 1));
|
||||
if (*i == "-webviewhelper" && std::distance(i, e) > 2) {
|
||||
Webview::WebKit2Gtk::SetDebug(*(i + 1));
|
||||
Webview::WebKit2Gtk::SetSocketPath(*(i + 2));
|
||||
return Webview::WebKit2Gtk::Exec();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,16 +59,6 @@ namespace {
|
||||
using internal::WaylandIntegration;
|
||||
using WorkMode = Core::Settings::WorkMode;
|
||||
|
||||
constexpr auto kPanelTrayIconName = "telegram-panel"_cs;
|
||||
constexpr auto kMutePanelTrayIconName = "telegram-mute-panel"_cs;
|
||||
constexpr auto kAttentionPanelTrayIconName = "telegram-attention-panel"_cs;
|
||||
|
||||
bool TrayIconMuted = true;
|
||||
int32 TrayIconCount = 0;
|
||||
base::flat_map<int, QImage> TrayIconImageBack;
|
||||
QIcon TrayIcon;
|
||||
QString TrayIconThemeName, TrayIconName;
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
void XCBSkipTaskbar(QWindow *window, bool skip) {
|
||||
const auto connection = base::Platform::XCB::GetConnectionFromQt();
|
||||
@@ -164,186 +154,6 @@ void SkipTaskbar(QWindow *window, bool skip) {
|
||||
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
}
|
||||
|
||||
QString GetPanelIconName(int counter, bool muted) {
|
||||
return (counter > 0)
|
||||
? (muted
|
||||
? kMutePanelTrayIconName.utf16()
|
||||
: kAttentionPanelTrayIconName.utf16())
|
||||
: kPanelTrayIconName.utf16();
|
||||
}
|
||||
|
||||
QString GetTrayIconName(int counter, bool muted) {
|
||||
const auto iconName = GetIconName();
|
||||
const auto panelIconName = GetPanelIconName(counter, muted);
|
||||
|
||||
if (QIcon::hasThemeIcon(panelIconName)) {
|
||||
return panelIconName;
|
||||
} else if (QIcon::hasThemeIcon(iconName)) {
|
||||
return iconName;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
int GetCounterSlice(int counter) {
|
||||
return (counter >= 1000)
|
||||
? (1000 + (counter % 100))
|
||||
: counter;
|
||||
}
|
||||
|
||||
bool IsIconRegenerationNeeded(
|
||||
int counter,
|
||||
bool muted,
|
||||
const QString &iconThemeName = QIcon::themeName()) {
|
||||
const auto iconName = GetTrayIconName(counter, muted);
|
||||
const auto counterSlice = GetCounterSlice(counter);
|
||||
|
||||
return TrayIcon.isNull()
|
||||
|| iconThemeName != TrayIconThemeName
|
||||
|| iconName != TrayIconName
|
||||
|| muted != TrayIconMuted
|
||||
|| counterSlice != TrayIconCount;
|
||||
}
|
||||
|
||||
void UpdateIconRegenerationNeeded(
|
||||
const QIcon &icon,
|
||||
int counter,
|
||||
bool muted,
|
||||
const QString &iconThemeName) {
|
||||
const auto iconName = GetTrayIconName(counter, muted);
|
||||
const auto counterSlice = GetCounterSlice(counter);
|
||||
|
||||
TrayIcon = icon;
|
||||
TrayIconMuted = muted;
|
||||
TrayIconCount = counterSlice;
|
||||
TrayIconThemeName = iconThemeName;
|
||||
TrayIconName = iconName;
|
||||
}
|
||||
|
||||
QIcon TrayIconGen(int counter, bool muted) {
|
||||
const auto iconThemeName = QIcon::themeName();
|
||||
|
||||
if (!IsIconRegenerationNeeded(counter, muted, iconThemeName)) {
|
||||
return TrayIcon;
|
||||
}
|
||||
|
||||
const auto iconName = GetTrayIconName(counter, muted);
|
||||
const auto panelIconName = GetPanelIconName(counter, muted);
|
||||
|
||||
if (iconName == panelIconName) {
|
||||
const auto result = QIcon::fromTheme(iconName);
|
||||
UpdateIconRegenerationNeeded(result, counter, muted, iconThemeName);
|
||||
return result;
|
||||
}
|
||||
|
||||
QIcon result;
|
||||
QIcon systemIcon;
|
||||
|
||||
static const auto iconSizes = {
|
||||
16,
|
||||
22,
|
||||
24,
|
||||
32,
|
||||
48,
|
||||
};
|
||||
|
||||
static const auto dprSize = [](const QImage &image) {
|
||||
return image.size() / image.devicePixelRatio();
|
||||
};
|
||||
|
||||
for (const auto iconSize : iconSizes) {
|
||||
auto ¤tImageBack = TrayIconImageBack[iconSize];
|
||||
const auto desiredSize = QSize(iconSize, iconSize);
|
||||
|
||||
if (currentImageBack.isNull()
|
||||
|| iconThemeName != TrayIconThemeName
|
||||
|| iconName != TrayIconName) {
|
||||
if (!iconName.isEmpty()) {
|
||||
if (systemIcon.isNull()) {
|
||||
systemIcon = QIcon::fromTheme(iconName);
|
||||
}
|
||||
|
||||
// We can't use QIcon::actualSize here
|
||||
// since it works incorrectly with svg icon themes
|
||||
currentImageBack = systemIcon
|
||||
.pixmap(desiredSize)
|
||||
.toImage();
|
||||
|
||||
const auto firstAttemptSize = dprSize(currentImageBack);
|
||||
|
||||
// if current icon theme is not a svg one, Qt can return
|
||||
// a pixmap that less in size even if there are a bigger one
|
||||
if (firstAttemptSize.width() < desiredSize.width()) {
|
||||
const auto availableSizes = systemIcon.availableSizes();
|
||||
|
||||
const auto biggestSize = ranges::max_element(
|
||||
availableSizes,
|
||||
std::less<>(),
|
||||
&QSize::width);
|
||||
|
||||
if (biggestSize->width() > firstAttemptSize.width()) {
|
||||
currentImageBack = systemIcon
|
||||
.pixmap(*biggestSize)
|
||||
.toImage();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentImageBack = Window::Logo();
|
||||
}
|
||||
|
||||
if (dprSize(currentImageBack) != desiredSize) {
|
||||
currentImageBack = currentImageBack.scaled(
|
||||
desiredSize * currentImageBack.devicePixelRatio(),
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
}
|
||||
|
||||
auto iconImage = currentImageBack;
|
||||
|
||||
if (counter > 0) {
|
||||
const auto &bg = muted
|
||||
? st::trayCounterBgMute
|
||||
: st::trayCounterBg;
|
||||
const auto &fg = st::trayCounterFg;
|
||||
if (iconSize >= 22) {
|
||||
const auto layerSize = (iconSize >= 48)
|
||||
? 32
|
||||
: (iconSize >= 36)
|
||||
? 24
|
||||
: (iconSize >= 32)
|
||||
? 20
|
||||
: 16;
|
||||
const auto layer = Window::GenerateCounterLayer({
|
||||
.size = layerSize,
|
||||
.count = counter,
|
||||
.bg = bg,
|
||||
.fg = fg,
|
||||
});
|
||||
|
||||
QPainter p(&iconImage);
|
||||
p.drawImage(
|
||||
iconImage.width() - layer.width() - 1,
|
||||
iconImage.height() - layer.height() - 1,
|
||||
layer);
|
||||
} else {
|
||||
iconImage = Window::WithSmallCounter(std::move(iconImage), {
|
||||
.size = 16,
|
||||
.count = counter,
|
||||
.bg = bg,
|
||||
.fg = fg,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result.addPixmap(Ui::PixmapFromImage(std::move(iconImage)));
|
||||
}
|
||||
|
||||
UpdateIconRegenerationNeeded(result, counter, muted, iconThemeName);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void SendKeySequence(
|
||||
Qt::Key key,
|
||||
Qt::KeyboardModifiers modifiers = Qt::NoModifier) {
|
||||
@@ -445,48 +255,14 @@ void MainWindow::initHook() {
|
||||
LOG(("System tray available: %1").arg(Logs::b(TrayIconSupported())));
|
||||
}
|
||||
|
||||
bool MainWindow::hasTrayIcon() const {
|
||||
return trayIcon;
|
||||
}
|
||||
|
||||
bool MainWindow::isActiveForTrayMenu() {
|
||||
updateIsActive();
|
||||
return Platform::IsWayland() ? isVisible() : isActive();
|
||||
}
|
||||
|
||||
void MainWindow::psShowTrayMenu() {
|
||||
_trayIconMenuXEmbed->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void MainWindow::psTrayMenuUpdated() {
|
||||
}
|
||||
|
||||
void MainWindow::psSetupTrayIcon() {
|
||||
if (!trayIcon) {
|
||||
trayIcon = new QSystemTrayIcon(this);
|
||||
trayIcon->setContextMenu(trayIconMenu);
|
||||
trayIcon->setIcon(TrayIconGen(
|
||||
Core::App().unreadBadge(),
|
||||
Core::App().unreadBadgeMuted()));
|
||||
|
||||
attachToTrayIcon(trayIcon);
|
||||
}
|
||||
updateIconCounters();
|
||||
|
||||
trayIcon->show();
|
||||
}
|
||||
|
||||
void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
|
||||
if (!TrayIconSupported()) {
|
||||
return;
|
||||
} else if (mode == WorkMode::WindowOnly) {
|
||||
if (trayIcon) {
|
||||
trayIcon->setContextMenu(0);
|
||||
trayIcon->deleteLater();
|
||||
}
|
||||
trayIcon = nullptr;
|
||||
} else {
|
||||
psSetupTrayIcon();
|
||||
}
|
||||
|
||||
SkipTaskbar(windowHandle(), mode == WorkMode::TrayOnly);
|
||||
@@ -497,9 +273,6 @@ void MainWindow::unreadCounterChangedHook() {
|
||||
}
|
||||
|
||||
void MainWindow::updateIconCounters() {
|
||||
const auto counter = Core::App().unreadBadge();
|
||||
const auto muted = Core::App().unreadBadgeMuted();
|
||||
|
||||
updateWindowIcon();
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
@@ -507,7 +280,7 @@ void MainWindow::updateIconCounters() {
|
||||
const auto launcherUrl = Glib::ustring(
|
||||
"application://"
|
||||
+ QGuiApplication::desktopFileName().toStdString());
|
||||
const auto counterSlice = std::min(counter, 9999);
|
||||
const auto counterSlice = std::min(Core::App().unreadBadge(), 9999);
|
||||
std::map<Glib::ustring, Glib::VariantBase> dbusUnityProperties;
|
||||
|
||||
if (counterSlice > 0) {
|
||||
@@ -541,15 +314,6 @@ void MainWindow::updateIconCounters() {
|
||||
}
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
if (trayIcon && IsIconRegenerationNeeded(counter, muted)) {
|
||||
trayIcon->setIcon(TrayIconGen(counter, muted));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::initTrayMenuHook() {
|
||||
_trayIconMenuXEmbed.emplace(nullptr, trayIconMenu);
|
||||
_trayIconMenuXEmbed->deleteOnHide(false);
|
||||
}
|
||||
|
||||
void MainWindow::createGlobalMenu() {
|
||||
@@ -820,17 +584,8 @@ void MainWindow::updateGlobalMenuHook() {
|
||||
}
|
||||
|
||||
bool MainWindow::eventFilter(QObject *obj, QEvent *evt) {
|
||||
QEvent::Type t = evt->type();
|
||||
if (t == QEvent::MouseButtonPress
|
||||
&& obj->objectName() == qstr("QSystemTrayIconSys")) {
|
||||
const auto ee = static_cast<QMouseEvent*>(evt);
|
||||
if (ee->button() == Qt::RightButton) {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
handleTrayIconActication(QSystemTrayIcon::Context);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
} else if (t == QEvent::FocusIn || t == QEvent::FocusOut) {
|
||||
const auto t = evt->type();
|
||||
if (t == QEvent::FocusIn || t == QEvent::FocusOut) {
|
||||
if (qobject_cast<QLineEdit*>(obj)
|
||||
|| qobject_cast<QTextEdit*>(obj)
|
||||
|| dynamic_cast<HistoryInner*>(obj)) {
|
||||
|
||||
@@ -22,8 +22,6 @@ class MainWindow : public Window::MainWindow {
|
||||
public:
|
||||
explicit MainWindow(not_null<Window::Controller*> controller);
|
||||
|
||||
void psShowTrayMenu();
|
||||
|
||||
bool isActiveForTrayMenu() override;
|
||||
|
||||
~MainWindow();
|
||||
@@ -35,21 +33,10 @@ protected:
|
||||
void unreadCounterChangedHook() override;
|
||||
void updateGlobalMenuHook() override;
|
||||
|
||||
void initTrayMenuHook() override;
|
||||
bool hasTrayIcon() const override;
|
||||
|
||||
void workmodeUpdated(Core::Settings::WorkMode mode) override;
|
||||
void createGlobalMenu() override;
|
||||
|
||||
QSystemTrayIcon *trayIcon = nullptr;
|
||||
QMenu *trayIconMenu = nullptr;
|
||||
|
||||
void psTrayMenuUpdated();
|
||||
void psSetupTrayIcon();
|
||||
|
||||
private:
|
||||
base::unique_qptr<Ui::PopupMenu> _trayIconMenuXEmbed;
|
||||
|
||||
QMenuBar *psMainMenu = nullptr;
|
||||
QAction *psLogout = nullptr;
|
||||
QAction *psUndo = nullptr;
|
||||
|
||||
@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QSystemTrayIcon>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QProcess>
|
||||
#include <QtGui/QWindow>
|
||||
@@ -64,7 +65,6 @@ constexpr auto kDesktopFile = ":/misc/telegramdesktop.desktop"_cs;
|
||||
constexpr auto kIconName = "telegram"_cs;
|
||||
|
||||
constexpr auto kIBusPortalService = "org.freedesktop.portal.IBus"_cs;
|
||||
constexpr auto kWebviewService = "org.telegram.desktop.GtkIntegration.WebviewHelper-%1-%2"_cs;
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
void PortalAutostart(bool start, bool silent) {
|
||||
|
||||
427
Telegram/SourceFiles/platform/linux/tray_linux.cpp
Normal file
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/tray_linux.h"
|
||||
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
#include "core/application.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "platform/linux/specific_linux.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "styles/style_window.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtWidgets/QMenu>
|
||||
#include <QtWidgets/QSystemTrayIcon>
|
||||
|
||||
namespace Platform {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] QWidget *Parent() {
|
||||
Expects(Core::App().primaryWindow() != nullptr);
|
||||
return Core::App().primaryWindow()->widget();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class IconGraphic final {
|
||||
public:
|
||||
explicit IconGraphic();
|
||||
~IconGraphic();
|
||||
|
||||
[[nodiscard]] bool isRefreshNeeded(
|
||||
int counter,
|
||||
bool muted,
|
||||
const QString &iconThemeName) const;
|
||||
[[nodiscard]] QIcon trayIcon(int counter, bool muted);
|
||||
|
||||
private:
|
||||
[[nodiscard]] QString panelIconName(int counter, bool muted) const;
|
||||
[[nodiscard]] QString trayIconName(int counter, bool muted) const;
|
||||
[[nodiscard]] int counterSlice(int counter) const;
|
||||
void updateIconRegenerationNeeded(
|
||||
const QIcon &icon,
|
||||
int counter,
|
||||
bool muted,
|
||||
const QString &iconThemeName);
|
||||
[[nodiscard]] QSize dprSize(const QImage &image) const;
|
||||
|
||||
const QString _panelTrayIconName;
|
||||
const QString _mutePanelTrayIconName;
|
||||
const QString _attentionPanelTrayIconName;
|
||||
|
||||
const int _iconSizes[5];
|
||||
|
||||
bool _muted = true;
|
||||
int32 _count = 0;
|
||||
base::flat_map<int, QImage> _imageBack;
|
||||
QIcon _trayIcon;
|
||||
QString _themeName;
|
||||
QString _name;
|
||||
|
||||
};
|
||||
|
||||
IconGraphic::IconGraphic()
|
||||
: _panelTrayIconName("telegram-panel")
|
||||
, _mutePanelTrayIconName("telegram-mute-panel")
|
||||
, _attentionPanelTrayIconName("telegram-attention-panel")
|
||||
, _iconSizes{ 16, 22, 24, 32, 48 } {
|
||||
}
|
||||
|
||||
IconGraphic::~IconGraphic() = default;
|
||||
|
||||
QString IconGraphic::panelIconName(int counter, bool muted) const {
|
||||
return (counter > 0)
|
||||
? (muted
|
||||
? _mutePanelTrayIconName
|
||||
: _attentionPanelTrayIconName)
|
||||
: _panelTrayIconName;
|
||||
}
|
||||
|
||||
QString IconGraphic::trayIconName(int counter, bool muted) const {
|
||||
const auto iconName = GetIconName();
|
||||
const auto panelName = panelIconName(counter, muted);
|
||||
|
||||
if (QIcon::hasThemeIcon(panelName)) {
|
||||
return panelName;
|
||||
} else if (QIcon::hasThemeIcon(iconName)) {
|
||||
return iconName;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
int IconGraphic::counterSlice(int counter) const {
|
||||
return (counter >= 1000)
|
||||
? (1000 + (counter % 100))
|
||||
: counter;
|
||||
}
|
||||
|
||||
bool IconGraphic::isRefreshNeeded(
|
||||
int counter,
|
||||
bool muted,
|
||||
const QString &iconThemeName) const {
|
||||
const auto iconName = trayIconName(counter, muted);
|
||||
|
||||
return _trayIcon.isNull()
|
||||
|| iconThemeName != _themeName
|
||||
|| iconName != _name
|
||||
|| muted != _muted
|
||||
|| counterSlice(counter) != _count;
|
||||
}
|
||||
|
||||
void IconGraphic::updateIconRegenerationNeeded(
|
||||
const QIcon &icon,
|
||||
int counter,
|
||||
bool muted,
|
||||
const QString &iconThemeName) {
|
||||
const auto iconName = trayIconName(counter, muted);
|
||||
|
||||
_trayIcon = icon;
|
||||
_muted = muted;
|
||||
_count = counterSlice(counter);
|
||||
_themeName = iconThemeName;
|
||||
_name = iconName;
|
||||
}
|
||||
|
||||
QSize IconGraphic::dprSize(const QImage &image) const {
|
||||
return image.size() / image.devicePixelRatio();
|
||||
}
|
||||
|
||||
QIcon IconGraphic::trayIcon(int counter, bool muted) {
|
||||
const auto iconThemeName = QIcon::themeName();
|
||||
|
||||
if (!isRefreshNeeded(counter, muted, iconThemeName)) {
|
||||
return _trayIcon;
|
||||
}
|
||||
|
||||
const auto iconName = trayIconName(counter, muted);
|
||||
|
||||
if (iconName == panelIconName(counter, muted)) {
|
||||
const auto result = QIcon::fromTheme(iconName);
|
||||
updateIconRegenerationNeeded(result, counter, muted, iconThemeName);
|
||||
return result;
|
||||
}
|
||||
|
||||
QIcon result;
|
||||
QIcon systemIcon;
|
||||
|
||||
for (const auto iconSize : _iconSizes) {
|
||||
auto ¤tImageBack = _imageBack[iconSize];
|
||||
const auto desiredSize = QSize(iconSize, iconSize);
|
||||
|
||||
if (currentImageBack.isNull()
|
||||
|| iconThemeName != _themeName
|
||||
|| iconName != _name) {
|
||||
if (!iconName.isEmpty()) {
|
||||
if (systemIcon.isNull()) {
|
||||
systemIcon = QIcon::fromTheme(iconName);
|
||||
}
|
||||
|
||||
// We can't use QIcon::actualSize here
|
||||
// since it works incorrectly with svg icon themes
|
||||
currentImageBack = systemIcon
|
||||
.pixmap(desiredSize)
|
||||
.toImage();
|
||||
|
||||
const auto firstAttemptSize = dprSize(currentImageBack);
|
||||
|
||||
// if current icon theme is not a svg one, Qt can return
|
||||
// a pixmap that less in size even if there are a bigger one
|
||||
if (firstAttemptSize.width() < desiredSize.width()) {
|
||||
const auto availableSizes = systemIcon.availableSizes();
|
||||
|
||||
const auto biggestSize = ranges::max_element(
|
||||
availableSizes,
|
||||
std::less<>(),
|
||||
&QSize::width);
|
||||
|
||||
if (biggestSize->width() > firstAttemptSize.width()) {
|
||||
currentImageBack = systemIcon
|
||||
.pixmap(*biggestSize)
|
||||
.toImage();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentImageBack = Window::Logo();
|
||||
}
|
||||
|
||||
if (dprSize(currentImageBack) != desiredSize) {
|
||||
currentImageBack = currentImageBack.scaled(
|
||||
desiredSize * currentImageBack.devicePixelRatio(),
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
}
|
||||
|
||||
auto iconImage = currentImageBack;
|
||||
|
||||
if (counter > 0) {
|
||||
const auto &bg = muted
|
||||
? st::trayCounterBgMute
|
||||
: st::trayCounterBg;
|
||||
const auto &fg = st::trayCounterFg;
|
||||
if (iconSize >= 22) {
|
||||
const auto layerSize = (iconSize >= 48)
|
||||
? 32
|
||||
: (iconSize >= 36)
|
||||
? 24
|
||||
: (iconSize >= 32)
|
||||
? 20
|
||||
: 16;
|
||||
const auto layer = Window::GenerateCounterLayer({
|
||||
.size = layerSize,
|
||||
.count = counter,
|
||||
.bg = bg,
|
||||
.fg = fg,
|
||||
});
|
||||
|
||||
QPainter p(&iconImage);
|
||||
p.drawImage(
|
||||
iconImage.width() - layer.width() - 1,
|
||||
iconImage.height() - layer.height() - 1,
|
||||
layer);
|
||||
} else {
|
||||
iconImage = Window::WithSmallCounter(std::move(iconImage), {
|
||||
.size = 16,
|
||||
.count = counter,
|
||||
.bg = bg,
|
||||
.fg = fg,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result.addPixmap(Ui::PixmapFromImage(std::move(iconImage)));
|
||||
}
|
||||
|
||||
updateIconRegenerationNeeded(result, counter, muted, iconThemeName);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class TrayEventFilter final : public QObject {
|
||||
public:
|
||||
TrayEventFilter(not_null<QObject*> parent);
|
||||
|
||||
[[nodiscard]] rpl::producer<> contextMenuFilters() const;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
private:
|
||||
const QString _iconObjectName;
|
||||
rpl::event_stream<> _contextMenuFilters;
|
||||
|
||||
};
|
||||
|
||||
TrayEventFilter::TrayEventFilter(not_null<QObject*> parent)
|
||||
: QObject(parent)
|
||||
, _iconObjectName("QSystemTrayIconSys") {
|
||||
parent->installEventFilter(this);
|
||||
}
|
||||
|
||||
bool TrayEventFilter::eventFilter(QObject *obj, QEvent *event) {
|
||||
if (event->type() == QEvent::MouseButtonPress
|
||||
&& obj->objectName() == _iconObjectName) {
|
||||
const auto m = static_cast<QMouseEvent*>(event);
|
||||
if (m->button() == Qt::RightButton) {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
_contextMenuFilters.fire({});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
rpl::producer<> TrayEventFilter::contextMenuFilters() const {
|
||||
return _contextMenuFilters.events();
|
||||
}
|
||||
|
||||
Tray::Tray() {
|
||||
}
|
||||
|
||||
void Tray::createIcon() {
|
||||
if (!_icon) {
|
||||
if (!_iconGraphic) {
|
||||
_iconGraphic = std::make_unique<IconGraphic>();
|
||||
}
|
||||
|
||||
const auto showXEmbed = [=] {
|
||||
_aboutToShowRequests.fire({});
|
||||
InvokeQueued(_menuXEmbed.get(), [=] {
|
||||
_menuXEmbed->popup(QCursor::pos());
|
||||
});
|
||||
};
|
||||
|
||||
_icon = base::make_unique_q<QSystemTrayIcon>(Parent());
|
||||
_icon->setIcon(_iconGraphic->trayIcon(
|
||||
Core::App().unreadBadge(),
|
||||
Core::App().unreadBadgeMuted()));
|
||||
_icon->setToolTip(AppName.utf16());
|
||||
|
||||
using Reason = QSystemTrayIcon::ActivationReason;
|
||||
base::qt_signal_producer(
|
||||
_icon.get(),
|
||||
&QSystemTrayIcon::activated
|
||||
) | rpl::start_with_next([=](Reason reason) {
|
||||
if (reason == QSystemTrayIcon::Context) {
|
||||
showXEmbed();
|
||||
} else {
|
||||
_iconClicks.fire({});
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
_icon->setContextMenu(_menu.get());
|
||||
|
||||
if (!_eventFilter) {
|
||||
_eventFilter = base::make_unique_q<TrayEventFilter>(
|
||||
QCoreApplication::instance());
|
||||
_eventFilter->contextMenuFilters(
|
||||
) | rpl::start_with_next([=] {
|
||||
showXEmbed();
|
||||
}, _lifetime);
|
||||
}
|
||||
}
|
||||
updateIcon();
|
||||
|
||||
_icon->show();
|
||||
}
|
||||
|
||||
void Tray::destroyIcon() {
|
||||
_icon = nullptr;
|
||||
}
|
||||
|
||||
void Tray::updateIcon() {
|
||||
if (!_icon || !_iconGraphic) {
|
||||
return;
|
||||
}
|
||||
const auto counter = Core::App().unreadBadge();
|
||||
const auto muted = Core::App().unreadBadgeMuted();
|
||||
|
||||
if (_iconGraphic->isRefreshNeeded(counter, muted, QIcon::themeName())) {
|
||||
_icon->setIcon(_iconGraphic->trayIcon(counter, muted));
|
||||
}
|
||||
}
|
||||
|
||||
void Tray::createMenu() {
|
||||
if (!_menu) {
|
||||
_menu = base::make_unique_q<QMenu>(Parent());
|
||||
}
|
||||
if (!_menuXEmbed) {
|
||||
_menuXEmbed = base::make_unique_q<Ui::PopupMenu>(nullptr);
|
||||
_menuXEmbed->deleteOnHide(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Tray::destroyMenu() {
|
||||
_menuXEmbed = nullptr;
|
||||
if (_menu) {
|
||||
_menu->clear();
|
||||
}
|
||||
_actionsLifetime.destroy();
|
||||
}
|
||||
|
||||
void Tray::addAction(rpl::producer<QString> text, Fn<void()> &&callback) {
|
||||
if (_menuXEmbed) {
|
||||
const auto XEAction = _menuXEmbed->addAction(QString(), callback);
|
||||
rpl::duplicate(
|
||||
text
|
||||
) | rpl::start_with_next([=](const QString &text) {
|
||||
XEAction->setText(text);
|
||||
}, _actionsLifetime);
|
||||
}
|
||||
|
||||
if (_menu) {
|
||||
const auto action = _menu->addAction(QString(), std::move(callback));
|
||||
std::move(
|
||||
text
|
||||
) | rpl::start_with_next([=](const QString &text) {
|
||||
action->setText(text);
|
||||
}, _actionsLifetime);
|
||||
}
|
||||
}
|
||||
|
||||
void Tray::showTrayMessage() const {
|
||||
}
|
||||
|
||||
bool Tray::hasTrayMessageSupport() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
rpl::producer<> Tray::aboutToShowRequests() const {
|
||||
return rpl::merge(
|
||||
_aboutToShowRequests.events(),
|
||||
_menu
|
||||
? base::qt_signal_producer(_menu.get(), &QMenu::aboutToShow)
|
||||
: rpl::never<>() | rpl::type_erased());
|
||||
}
|
||||
|
||||
rpl::producer<> Tray::showFromTrayRequests() const {
|
||||
return rpl::never<>();
|
||||
}
|
||||
|
||||
rpl::producer<> Tray::hideToTrayRequests() const {
|
||||
return rpl::never<>();
|
||||
}
|
||||
|
||||
rpl::producer<> Tray::iconClicks() const {
|
||||
return _iconClicks.events();
|
||||
}
|
||||
|
||||
rpl::lifetime &Tray::lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
Tray::~Tray() = default;
|
||||
|
||||
} // namespace Platform
|
||||
68
Telegram/SourceFiles/platform/linux/tray_linux.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "platform/platform_tray.h"
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
namespace Ui {
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
class QMenu;
|
||||
class QSystemTrayIcon;
|
||||
|
||||
namespace Platform {
|
||||
|
||||
class IconGraphic;
|
||||
class TrayEventFilter;
|
||||
|
||||
class Tray final {
|
||||
public:
|
||||
Tray();
|
||||
~Tray();
|
||||
|
||||
[[nodiscard]] rpl::producer<> aboutToShowRequests() const;
|
||||
[[nodiscard]] rpl::producer<> showFromTrayRequests() const;
|
||||
[[nodiscard]] rpl::producer<> hideToTrayRequests() const;
|
||||
[[nodiscard]] rpl::producer<> iconClicks() const;
|
||||
|
||||
void createIcon();
|
||||
void destroyIcon();
|
||||
|
||||
void updateIcon();
|
||||
|
||||
void createMenu();
|
||||
void destroyMenu();
|
||||
|
||||
void addAction(rpl::producer<QString> text, Fn<void()> &&callback);
|
||||
|
||||
void showTrayMessage() const;
|
||||
[[nodiscard]] bool hasTrayMessageSupport() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
std::unique_ptr<IconGraphic> _iconGraphic;
|
||||
|
||||
base::unique_qptr<QSystemTrayIcon> _icon;
|
||||
base::unique_qptr<QMenu> _menu;
|
||||
base::unique_qptr<Ui::PopupMenu> _menuXEmbed;
|
||||
|
||||
base::unique_qptr<TrayEventFilter> _eventFilter;
|
||||
|
||||
rpl::event_stream<> _iconClicks;
|
||||
rpl::event_stream<> _aboutToShowRequests;
|
||||
|
||||
rpl::lifetime _actionsLifetime;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Platform
|
||||
@@ -30,8 +30,6 @@ public:
|
||||
|
||||
void updateWindowIcon() override;
|
||||
|
||||
void psShowTrayMenu();
|
||||
|
||||
bool preventsQuit(Core::QuitReason reason) override;
|
||||
|
||||
class Private;
|
||||
@@ -39,25 +37,12 @@ public:
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *evt) override;
|
||||
|
||||
void handleActiveChangedHook() override;
|
||||
void stateChangedHook(Qt::WindowState state) override;
|
||||
void initHook() override;
|
||||
void unreadCounterChangedHook() override;
|
||||
|
||||
bool hasTrayIcon() const override {
|
||||
return trayIcon;
|
||||
}
|
||||
|
||||
void updateGlobalMenuHook() override;
|
||||
|
||||
void workmodeUpdated(Core::Settings::WorkMode mode) override;
|
||||
|
||||
QSystemTrayIcon *trayIcon = nullptr;
|
||||
QMenu *trayIconMenu = nullptr;
|
||||
|
||||
void psTrayMenuUpdated();
|
||||
void psSetupTrayIcon();
|
||||
|
||||
void closeWithoutDestroy() override;
|
||||
void createGlobalMenu() override;
|
||||
|
||||
@@ -66,7 +51,6 @@ private:
|
||||
|
||||
void hideAndDeactivate();
|
||||
void updateIconCounters();
|
||||
[[nodiscard]] QIcon generateIconForTray(int counter, bool muted) const;
|
||||
|
||||
std::unique_ptr<Private> _private;
|
||||
|
||||
|
||||
@@ -65,17 +65,6 @@ namespace {
|
||||
// fullscreen mode, after that we'll hide the window no matter what.
|
||||
constexpr auto kHideAfterFullscreenTimeoutMs = 3000;
|
||||
|
||||
[[nodiscard]] QImage TrayIconBack(bool darkMode) {
|
||||
static const auto WithColor = [](QColor color) {
|
||||
return st::macTrayIcon.instance(color, 100);
|
||||
};
|
||||
static const auto DarkModeResult = WithColor({ 255, 255, 255 });
|
||||
static const auto LightModeResult = WithColor({ 0, 0, 0, 180 });
|
||||
auto result = darkMode ? DarkModeResult : LightModeResult;
|
||||
result.detach();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class MainWindow::Private {
|
||||
@@ -248,20 +237,6 @@ void MainWindow::stateChangedHook(Qt::WindowState state) {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::handleActiveChangedHook() {
|
||||
// On macOS just remove trayIcon menu if the window is not active.
|
||||
// So we will activate the window on click instead of showing the menu.
|
||||
if (isActiveForTrayMenu()) {
|
||||
if (trayIcon
|
||||
&& trayIconMenu
|
||||
&& trayIcon->contextMenu() != trayIconMenu) {
|
||||
trayIcon->setContextMenu(trayIconMenu);
|
||||
}
|
||||
} else if (trayIcon) {
|
||||
trayIcon->setContextMenu(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::initHook() {
|
||||
_customTitleHeight = 0;
|
||||
if (auto view = reinterpret_cast<NSView*>(winId())) {
|
||||
@@ -282,9 +257,6 @@ void MainWindow::hideAndDeactivate() {
|
||||
hide();
|
||||
}
|
||||
|
||||
void MainWindow::psShowTrayMenu() {
|
||||
}
|
||||
|
||||
bool MainWindow::preventsQuit(Core::QuitReason reason) {
|
||||
// Thanks Chromium, see
|
||||
// chromium.org/developers/design-documents/confirm-to-quit-experiment
|
||||
@@ -298,87 +270,12 @@ bool MainWindow::preventsQuit(Core::QuitReason reason) {
|
||||
Platform::ConfirmQuit::QuitKeysString()));
|
||||
}
|
||||
|
||||
void MainWindow::psTrayMenuUpdated() {
|
||||
}
|
||||
|
||||
void MainWindow::psSetupTrayIcon() {
|
||||
if (!trayIcon) {
|
||||
trayIcon = new QSystemTrayIcon(this);
|
||||
trayIcon->setIcon(generateIconForTray(
|
||||
Core::App().unreadBadge(),
|
||||
Core::App().unreadBadgeMuted()));
|
||||
if (isActiveForTrayMenu()) {
|
||||
trayIcon->setContextMenu(trayIconMenu);
|
||||
} else {
|
||||
trayIcon->setContextMenu(nullptr);
|
||||
}
|
||||
attachToTrayIcon(trayIcon);
|
||||
} else {
|
||||
updateIconCounters();
|
||||
}
|
||||
|
||||
trayIcon->show();
|
||||
}
|
||||
|
||||
void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
|
||||
psSetupTrayIcon();
|
||||
if (mode == Core::Settings::WorkMode::WindowOnly) {
|
||||
if (trayIcon) {
|
||||
trayIcon->setContextMenu(0);
|
||||
delete trayIcon;
|
||||
trayIcon = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _placeCounter(QImage &img, int size, int count, style::color bg, style::color color) {
|
||||
if (!count) return;
|
||||
auto savedRatio = img.devicePixelRatio();
|
||||
img.setDevicePixelRatio(1.);
|
||||
|
||||
{
|
||||
Painter p(&img);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
auto cnt = (count < 100) ? QString("%1").arg(count) : QString("..%1").arg(count % 100, 2, 10, QChar('0'));
|
||||
auto cntSize = cnt.size();
|
||||
|
||||
p.setBrush(bg);
|
||||
p.setPen(Qt::NoPen);
|
||||
int32 fontSize, skip;
|
||||
if (size == 22) {
|
||||
skip = 1;
|
||||
fontSize = 8;
|
||||
} else {
|
||||
skip = 2;
|
||||
fontSize = 16;
|
||||
}
|
||||
style::font f(fontSize, 0, 0);
|
||||
int32 w = f->width(cnt), d, r;
|
||||
if (size == 22) {
|
||||
d = (cntSize < 2) ? 3 : 2;
|
||||
r = (cntSize < 2) ? 6 : 5;
|
||||
} else {
|
||||
d = (cntSize < 2) ? 6 : 5;
|
||||
r = (cntSize < 2) ? 9 : 11;
|
||||
}
|
||||
p.drawRoundedRect(QRect(size - w - d * 2 - skip, size - f->height - skip, w + d * 2, f->height), r, r);
|
||||
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.setFont(f);
|
||||
p.setPen(color);
|
||||
p.drawText(size - w - d - skip, size - f->height + f->ascent - skip, cnt);
|
||||
}
|
||||
img.setDevicePixelRatio(savedRatio);
|
||||
}
|
||||
|
||||
void MainWindow::unreadCounterChangedHook() {
|
||||
updateIconCounters();
|
||||
}
|
||||
|
||||
void MainWindow::updateIconCounters() {
|
||||
const auto counter = Core::App().unreadBadge();
|
||||
const auto muted = Core::App().unreadBadgeMuted();
|
||||
|
||||
const auto string = !counter
|
||||
? QString()
|
||||
@@ -386,43 +283,6 @@ void MainWindow::updateIconCounters() {
|
||||
? QString("%1").arg(counter)
|
||||
: QString("..%1").arg(counter % 100, 2, 10, QChar('0'));
|
||||
_private->setWindowBadge(string);
|
||||
|
||||
if (trayIcon) {
|
||||
trayIcon->setIcon(generateIconForTray(counter, muted));
|
||||
}
|
||||
}
|
||||
|
||||
QIcon MainWindow::generateIconForTray(int counter, bool muted) const {
|
||||
auto result = QIcon();
|
||||
auto lightMode = TrayIconBack(false);
|
||||
auto darkMode = TrayIconBack(true);
|
||||
auto lightModeActive = darkMode;
|
||||
auto darkModeActive = darkMode;
|
||||
lightModeActive.detach();
|
||||
darkModeActive.detach();
|
||||
const auto size = 22 * cIntRetinaFactor();
|
||||
const auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg);
|
||||
_placeCounter(lightMode, size, counter, bg, st::trayCounterFg);
|
||||
_placeCounter(darkMode, size, counter, bg, muted ? st::trayCounterFgMacInvert : st::trayCounterFg);
|
||||
_placeCounter(lightModeActive, size, counter, st::trayCounterBgMacInvert, st::trayCounterFgMacInvert);
|
||||
_placeCounter(darkModeActive, size, counter, st::trayCounterBgMacInvert, st::trayCounterFgMacInvert);
|
||||
result.addPixmap(Ui::PixmapFromImage(
|
||||
std::move(lightMode)),
|
||||
QIcon::Normal,
|
||||
QIcon::Off);
|
||||
result.addPixmap(Ui::PixmapFromImage(
|
||||
std::move(darkMode)),
|
||||
QIcon::Normal,
|
||||
QIcon::On);
|
||||
result.addPixmap(Ui::PixmapFromImage(
|
||||
std::move(lightModeActive)),
|
||||
QIcon::Active,
|
||||
QIcon::Off);
|
||||
result.addPixmap(Ui::PixmapFromImage(
|
||||
std::move(darkModeActive)),
|
||||
QIcon::Active,
|
||||
QIcon::On);
|
||||
return result;
|
||||
}
|
||||
|
||||
void MainWindow::createGlobalMenu() {
|
||||
|
||||
56
Telegram/SourceFiles/platform/mac/tray_mac.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "platform/platform_tray.h"
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
class QMenu;
|
||||
|
||||
namespace Platform {
|
||||
|
||||
class NativeIcon;
|
||||
|
||||
class Tray final {
|
||||
public:
|
||||
Tray();
|
||||
~Tray();
|
||||
|
||||
[[nodiscard]] rpl::producer<> aboutToShowRequests() const;
|
||||
[[nodiscard]] rpl::producer<> showFromTrayRequests() const;
|
||||
[[nodiscard]] rpl::producer<> hideToTrayRequests() const;
|
||||
[[nodiscard]] rpl::producer<> iconClicks() const;
|
||||
|
||||
void createIcon();
|
||||
void destroyIcon();
|
||||
|
||||
void updateIcon();
|
||||
|
||||
void createMenu();
|
||||
void destroyMenu();
|
||||
|
||||
void addAction(rpl::producer<QString> text, Fn<void()> &&callback);
|
||||
|
||||
void showTrayMessage() const;
|
||||
[[nodiscard]] bool hasTrayMessageSupport() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
std::unique_ptr<NativeIcon> _nativeIcon;
|
||||
base::unique_qptr<QMenu> _menu;
|
||||
|
||||
rpl::event_stream<> _showFromTrayRequests;
|
||||
|
||||
rpl::lifetime _actionsLifetime;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Platform
|
||||
413
Telegram/SourceFiles/platform/mac/tray_mac.mm
Normal file
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/mac/tray_mac.h"
|
||||
|
||||
#include "base/platform/mac/base_utilities_mac.h"
|
||||
#include "core/application.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_window.h"
|
||||
|
||||
#include <QtWidgets/QMenu>
|
||||
|
||||
#import <AppKit/NSMenu.h>
|
||||
#import <AppKit/NSStatusItem.h>
|
||||
|
||||
@interface CommonDelegate : NSObject<NSMenuDelegate> {
|
||||
}
|
||||
|
||||
- (void) menuDidClose:(NSMenu *)menu;
|
||||
- (void) menuWillOpen:(NSMenu *)menu;
|
||||
- (void) observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
|
||||
context:(void *)context;
|
||||
|
||||
- (rpl::producer<>) closes;
|
||||
- (rpl::producer<>) aboutToShowRequests;
|
||||
- (rpl::producer<>) appearanceChanges;
|
||||
|
||||
@end // @interface CommonDelegate
|
||||
|
||||
@implementation CommonDelegate {
|
||||
rpl::event_stream<> _closes;
|
||||
rpl::event_stream<> _aboutToShowRequests;
|
||||
rpl::event_stream<> _appearanceChanges;
|
||||
}
|
||||
|
||||
- (void) menuDidClose:(NSMenu *)menu {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
_closes.fire({});
|
||||
});
|
||||
}
|
||||
|
||||
- (void) menuWillOpen:(NSMenu *)menu {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
_aboutToShowRequests.fire({});
|
||||
});
|
||||
}
|
||||
|
||||
// Thanks https://stackoverflow.com/a/64525038
|
||||
- (void) observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
|
||||
context:(void *)context {
|
||||
if ([keyPath isEqualToString:@"button.effectiveAppearance"]) {
|
||||
_appearanceChanges.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
- (rpl::producer<>) closes {
|
||||
return _closes.events();
|
||||
}
|
||||
|
||||
- (rpl::producer<>) aboutToShowRequests {
|
||||
return _aboutToShowRequests.events();
|
||||
}
|
||||
|
||||
- (rpl::producer<>) appearanceChanges {
|
||||
return _appearanceChanges.events();
|
||||
}
|
||||
|
||||
@end // @implementation MenuDelegate
|
||||
|
||||
namespace Platform {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] QImage TrayIconBack(bool darkMode) {
|
||||
static const auto WithColor = [](QColor color) {
|
||||
return st::macTrayIcon.instance(color, 100);
|
||||
};
|
||||
static const auto DarkModeResult = WithColor({ 255, 255, 255 });
|
||||
static const auto LightModeResult = WithColor({ 0, 0, 0, 180 });
|
||||
auto result = darkMode ? DarkModeResult : LightModeResult;
|
||||
result.detach();
|
||||
return result;
|
||||
}
|
||||
|
||||
void PlaceCounter(
|
||||
QImage &img,
|
||||
int size,
|
||||
int count,
|
||||
style::color bg,
|
||||
style::color color) {
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
const auto savedRatio = img.devicePixelRatio();
|
||||
img.setDevicePixelRatio(1.);
|
||||
|
||||
{
|
||||
Painter p(&img);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
const auto cnt = (count < 100)
|
||||
? QString("%1").arg(count)
|
||||
: QString("..%1").arg(count % 100, 2, 10, QChar('0'));
|
||||
const auto cntSize = cnt.size();
|
||||
|
||||
p.setBrush(bg);
|
||||
p.setPen(Qt::NoPen);
|
||||
int32 fontSize, skip;
|
||||
if (size == 22) {
|
||||
skip = 1;
|
||||
fontSize = 8;
|
||||
} else {
|
||||
skip = 2;
|
||||
fontSize = 16;
|
||||
}
|
||||
style::font f(fontSize, 0, 0);
|
||||
int32 w = f->width(cnt), d, r;
|
||||
if (size == 22) {
|
||||
d = (cntSize < 2) ? 3 : 2;
|
||||
r = (cntSize < 2) ? 6 : 5;
|
||||
} else {
|
||||
d = (cntSize < 2) ? 6 : 5;
|
||||
r = (cntSize < 2) ? 9 : 11;
|
||||
}
|
||||
p.drawRoundedRect(
|
||||
QRect(
|
||||
size - w - d * 2 - skip,
|
||||
size - f->height - skip,
|
||||
w + d * 2,
|
||||
f->height),
|
||||
r,
|
||||
r);
|
||||
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.setFont(f);
|
||||
p.setPen(color);
|
||||
p.drawText(
|
||||
size - w - d - skip,
|
||||
size - f->height + f->ascent - skip,
|
||||
cnt);
|
||||
}
|
||||
img.setDevicePixelRatio(savedRatio);
|
||||
}
|
||||
|
||||
void UpdateIcon(const NSStatusItem *status) {
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto appearance = status.button.effectiveAppearance;
|
||||
const auto darkMode = [[appearance.name lowercaseString]
|
||||
containsString:@"dark"];
|
||||
|
||||
// The recommended maximum title bar icon height is 18 points
|
||||
// (device independent pixels). The menu height on past and
|
||||
// current OS X versions is 22 points. Provide some future-proofing
|
||||
// by deriving the icon height from the menu height.
|
||||
const int padding = 0;
|
||||
const int menuHeight = NSStatusBar.systemStatusBar.thickness;
|
||||
// [[status.button window] backingScaleFactor];
|
||||
const int maxImageHeight = (menuHeight - padding)
|
||||
* style::DevicePixelRatio();
|
||||
|
||||
// Select pixmap based on the device pixel height. Ideally we would use
|
||||
// the devicePixelRatio of the target screen, but that value is not
|
||||
// known until draw time. Use qApp->devicePixelRatio, which returns the
|
||||
// devicePixelRatio for the "best" screen on the system.
|
||||
|
||||
const auto side = 22 * style::DevicePixelRatio();
|
||||
const auto selectedSize = QSize(side, side);
|
||||
|
||||
auto result = TrayIconBack(darkMode);
|
||||
auto resultActive = result;
|
||||
resultActive.detach();
|
||||
|
||||
const auto counter = Core::App().unreadBadge();
|
||||
const auto muted = Core::App().unreadBadgeMuted();
|
||||
|
||||
const auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg);
|
||||
const auto &fg = st::trayCounterFg;
|
||||
const auto &fgInvert = st::trayCounterFgMacInvert;
|
||||
const auto &bgInvert = st::trayCounterBgMacInvert;
|
||||
|
||||
const auto &resultFg = !darkMode ? fg : muted ? fgInvert : fg;
|
||||
PlaceCounter(result, side, counter, bg, resultFg);
|
||||
PlaceCounter(resultActive, side, counter, bgInvert, fgInvert);
|
||||
|
||||
// Scale large pixmaps to fit the available menu bar area.
|
||||
if (result.height() > maxImageHeight) {
|
||||
result = result.scaledToHeight(
|
||||
maxImageHeight,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
if (resultActive.height() > maxImageHeight) {
|
||||
resultActive = resultActive.scaledToHeight(
|
||||
maxImageHeight,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
status.button.image = Q2NSImage(result);
|
||||
status.button.alternateImage = Q2NSImage(resultActive);
|
||||
status.button.imageScaling = NSImageScaleProportionallyDown;
|
||||
}
|
||||
|
||||
[[nodiscard]] QWidget *Parent() {
|
||||
Expects(Core::App().primaryWindow() != nullptr);
|
||||
return Core::App().primaryWindow()->widget();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class NativeIcon final {
|
||||
public:
|
||||
NativeIcon();
|
||||
~NativeIcon();
|
||||
|
||||
void updateIcon();
|
||||
void showMenu(not_null<QMenu*> menu);
|
||||
void deactivateButton();
|
||||
|
||||
[[nodiscard]] rpl::producer<> clicks() const;
|
||||
[[nodiscard]] rpl::producer<> aboutToShowRequests() const;
|
||||
|
||||
private:
|
||||
CommonDelegate *_delegate;
|
||||
NSStatusItem *_status;
|
||||
|
||||
rpl::event_stream<> _clicks;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
NativeIcon::NativeIcon()
|
||||
: _delegate([[CommonDelegate alloc] init])
|
||||
, _status([
|
||||
[NSStatusBar.systemStatusBar
|
||||
statusItemWithLength:NSSquareStatusItemLength] retain]) {
|
||||
|
||||
[_status
|
||||
addObserver:_delegate
|
||||
forKeyPath:@"button.effectiveAppearance"
|
||||
options:0
|
||||
| NSKeyValueObservingOptionNew
|
||||
| NSKeyValueObservingOptionInitial
|
||||
context:nil];
|
||||
|
||||
[_delegate closes] | rpl::start_with_next([=] {
|
||||
_status.menu = nil;
|
||||
}, _lifetime);
|
||||
|
||||
[_delegate appearanceChanges] | rpl::start_with_next([=] {
|
||||
updateIcon();
|
||||
}, _lifetime);
|
||||
|
||||
const auto masks = NSEventMaskLeftMouseDown
|
||||
| NSEventMaskLeftMouseUp
|
||||
| NSEventMaskRightMouseDown
|
||||
| NSEventMaskRightMouseUp
|
||||
| NSEventMaskOtherMouseUp;
|
||||
[_status.button sendActionOn:masks];
|
||||
|
||||
id buttonCallback = [^{
|
||||
const auto type = NSApp.currentEvent.type;
|
||||
|
||||
if ((type == NSEventTypeLeftMouseDown)
|
||||
|| (type == NSEventTypeRightMouseDown)) {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([=] {
|
||||
_clicks.fire({});
|
||||
});
|
||||
}
|
||||
} copy];
|
||||
|
||||
_lifetime.add([=] {
|
||||
[buttonCallback release];
|
||||
});
|
||||
|
||||
_status.button.target = buttonCallback;
|
||||
_status.button.action = @selector(invoke);
|
||||
_status.button.toolTip = Q2NSString(AppName.utf16());
|
||||
}
|
||||
|
||||
NativeIcon::~NativeIcon() {
|
||||
[_status
|
||||
removeObserver:_delegate
|
||||
forKeyPath:@"button.effectiveAppearance"];
|
||||
[NSStatusBar.systemStatusBar removeStatusItem:_status];
|
||||
|
||||
[_status release];
|
||||
[_delegate release];
|
||||
}
|
||||
|
||||
void NativeIcon::updateIcon() {
|
||||
UpdateIcon(_status);
|
||||
}
|
||||
|
||||
void NativeIcon::showMenu(not_null<QMenu*> menu) {
|
||||
_status.menu = menu->toNSMenu();
|
||||
_status.menu.delegate = _delegate;
|
||||
[_status.button performClick:nil];
|
||||
}
|
||||
|
||||
void NativeIcon::deactivateButton() {
|
||||
[_status.button highlight:false];
|
||||
}
|
||||
|
||||
rpl::producer<> NativeIcon::clicks() const {
|
||||
return _clicks.events();
|
||||
}
|
||||
|
||||
rpl::producer<> NativeIcon::aboutToShowRequests() const {
|
||||
return [_delegate aboutToShowRequests];
|
||||
}
|
||||
|
||||
Tray::Tray() {
|
||||
}
|
||||
|
||||
void Tray::createIcon() {
|
||||
if (!_nativeIcon) {
|
||||
_nativeIcon = std::make_unique<NativeIcon>();
|
||||
// On macOS we are activating the window on click
|
||||
// instead of showing the menu, when the window is not activated.
|
||||
_nativeIcon->clicks(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (Core::App().isActiveForTrayMenu()) {
|
||||
_nativeIcon->showMenu(_menu.get());
|
||||
} else {
|
||||
_nativeIcon->deactivateButton();
|
||||
_showFromTrayRequests.fire({});
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
updateIcon();
|
||||
}
|
||||
|
||||
void Tray::destroyIcon() {
|
||||
_nativeIcon = nullptr;
|
||||
}
|
||||
|
||||
void Tray::updateIcon() {
|
||||
if (_nativeIcon) {
|
||||
_nativeIcon->updateIcon();
|
||||
}
|
||||
}
|
||||
|
||||
void Tray::createMenu() {
|
||||
if (!_menu) {
|
||||
_menu = base::make_unique_q<QMenu>(Parent());
|
||||
}
|
||||
}
|
||||
|
||||
void Tray::destroyMenu() {
|
||||
if (_menu) {
|
||||
_menu->clear();
|
||||
}
|
||||
_actionsLifetime.destroy();
|
||||
}
|
||||
|
||||
void Tray::addAction(rpl::producer<QString> text, Fn<void()> &&callback) {
|
||||
if (!_menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto action = _menu->addAction(QString(), std::move(callback));
|
||||
std::move(
|
||||
text
|
||||
) | rpl::start_with_next([=](const QString &text) {
|
||||
action->setText(text);
|
||||
}, _actionsLifetime);
|
||||
}
|
||||
|
||||
void Tray::showTrayMessage() const {
|
||||
}
|
||||
|
||||
bool Tray::hasTrayMessageSupport() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
rpl::producer<> Tray::aboutToShowRequests() const {
|
||||
return _nativeIcon
|
||||
? _nativeIcon->aboutToShowRequests()
|
||||
: rpl::never<>();
|
||||
}
|
||||
|
||||
rpl::producer<> Tray::showFromTrayRequests() const {
|
||||
return _showFromTrayRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<> Tray::hideToTrayRequests() const {
|
||||
return rpl::never<>();
|
||||
}
|
||||
|
||||
rpl::producer<> Tray::iconClicks() const {
|
||||
return rpl::never<>();
|
||||
}
|
||||
|
||||
rpl::lifetime &Tray::lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
Tray::~Tray() = default;
|
||||
|
||||
} // namespace Platform
|
||||
24
Telegram/SourceFiles/platform/platform_tray.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Platform {
|
||||
|
||||
class Tray;
|
||||
|
||||
} // namespace Platform
|
||||
|
||||
// Platform dependent implementations.
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include "platform/mac/tray_mac.h"
|
||||
#elif defined Q_OS_UNIX // Q_OS_MAC
|
||||
#include "platform/linux/tray_linux.h"
|
||||
#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_UNIX
|
||||
#include "platform/win/tray_win.h"
|
||||
#endif // Q_OS_MAC || Q_OS_UNIX || Q_OS_WIN
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_window.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "platform/platform_notifications_manager.h"
|
||||
#include "platform/win/tray_win.h"
|
||||
#include "platform/win/windows_dlls.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/window_session_controller.h"
|
||||
@@ -95,69 +96,6 @@ uint32 kTaskbarCreatedMsgId = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage IconWithCounter(
|
||||
Window::CounterLayerArgs &&args,
|
||||
Main::Session *session,
|
||||
bool smallIcon) {
|
||||
static constexpr auto kCount = 3;
|
||||
static auto ScaledLogo = std::array<QImage, kCount>();
|
||||
static auto ScaledLogoNoMargin = std::array<QImage, kCount>();
|
||||
|
||||
struct Dimensions {
|
||||
int index = 0;
|
||||
int size = 0;
|
||||
};
|
||||
const auto d = [&]() -> Dimensions {
|
||||
switch (args.size) {
|
||||
case 16:
|
||||
return {
|
||||
.index = 0,
|
||||
.size = 16,
|
||||
};
|
||||
case 32:
|
||||
return {
|
||||
.index = 1,
|
||||
.size = 32,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
.index = 2,
|
||||
.size = 64,
|
||||
};
|
||||
}
|
||||
}();
|
||||
Assert(d.index < kCount);
|
||||
|
||||
auto &scaled = smallIcon ? ScaledLogoNoMargin : ScaledLogo;
|
||||
auto result = [&] {
|
||||
auto &image = scaled[d.index];
|
||||
if (image.isNull()) {
|
||||
image = (smallIcon
|
||||
? Window::LogoNoMargin()
|
||||
: Window::Logo()).scaledToWidth(
|
||||
d.size,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
return image;
|
||||
}();
|
||||
if (session && session->supportMode()) {
|
||||
Window::ConvertIconToBlack(result);
|
||||
}
|
||||
if (!args.count) {
|
||||
return result;
|
||||
} else if (smallIcon) {
|
||||
return Window::WithSmallCounter(std::move(result), std::move(args));
|
||||
}
|
||||
QPainter p(&result);
|
||||
const auto half = d.size / 2;
|
||||
args.size = half;
|
||||
p.drawPixmap(
|
||||
half,
|
||||
half,
|
||||
Ui::PixmapFromImage(Window::GenerateCounterLayer(std::move(args))));
|
||||
return result;
|
||||
}
|
||||
|
||||
EventFilter::EventFilter(not_null<MainWindow*> window) : _window(window) {
|
||||
}
|
||||
|
||||
@@ -277,10 +215,6 @@ void MainWindow::shadowsDeactivate() {
|
||||
_hasActiveFrame = false;
|
||||
}
|
||||
|
||||
void MainWindow::psShowTrayMenu() {
|
||||
trayIconMenu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void MainWindow::destroyedFromSystem() {
|
||||
Core::Quit();
|
||||
}
|
||||
@@ -310,47 +244,11 @@ void MainWindow::forceIconRefresh() {
|
||||
updateIconCounters();
|
||||
}
|
||||
|
||||
void MainWindow::psTrayMenuUpdated() {
|
||||
}
|
||||
|
||||
void MainWindow::psSetupTrayIcon() {
|
||||
if (!trayIcon) {
|
||||
trayIcon = new QSystemTrayIcon(this);
|
||||
|
||||
const auto icon = QIcon(Ui::PixmapFromImage(
|
||||
QImage(Window::LogoNoMargin())));
|
||||
|
||||
trayIcon->setIcon(icon);
|
||||
connect(
|
||||
trayIcon,
|
||||
&QSystemTrayIcon::messageClicked,
|
||||
this,
|
||||
[=] { showFromTray(); });
|
||||
attachToTrayIcon(trayIcon);
|
||||
}
|
||||
updateIconCounters();
|
||||
|
||||
trayIcon->show();
|
||||
}
|
||||
|
||||
void MainWindow::showTrayTooltip() {
|
||||
if (trayIcon && !cSeenTrayTooltip()) {
|
||||
trayIcon->showMessage(
|
||||
AppName.utf16(),
|
||||
tr::lng_tray_icon_text(tr::now),
|
||||
QSystemTrayIcon::Information,
|
||||
10000);
|
||||
cSetSeenTrayTooltip(true);
|
||||
Local::writeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
|
||||
using WorkMode = Core::Settings::WorkMode;
|
||||
|
||||
switch (mode) {
|
||||
case WorkMode::WindowAndTray: {
|
||||
psSetupTrayIcon();
|
||||
HWND psOwner = (HWND)GetWindowLongPtr(_hWnd, GWLP_HWNDPARENT);
|
||||
if (psOwner) {
|
||||
SetWindowLongPtr(_hWnd, GWLP_HWNDPARENT, 0);
|
||||
@@ -360,7 +258,6 @@ void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
|
||||
} break;
|
||||
|
||||
case WorkMode::TrayOnly: {
|
||||
psSetupTrayIcon();
|
||||
HWND psOwner = (HWND)GetWindowLongPtr(_hWnd, GWLP_HWNDPARENT);
|
||||
if (!psOwner) {
|
||||
const auto hwnd = _taskbarHiderWindow->winId();
|
||||
@@ -370,12 +267,6 @@ void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
|
||||
} break;
|
||||
|
||||
case WorkMode::WindowOnly: {
|
||||
if (trayIcon) {
|
||||
trayIcon->setContextMenu(0);
|
||||
trayIcon->deleteLater();
|
||||
}
|
||||
trayIcon = 0;
|
||||
|
||||
HWND psOwner = (HWND)GetWindowLongPtr(_hWnd, GWLP_HWNDPARENT);
|
||||
if (psOwner) {
|
||||
SetWindowLongPtr(_hWnd, GWLP_HWNDPARENT, 0);
|
||||
@@ -446,40 +337,28 @@ void MainWindow::updateIconCounters() {
|
||||
const auto iconSizeBig = QSize(
|
||||
GetSystemMetrics(SM_CXICON),
|
||||
GetSystemMetrics(SM_CYICON));
|
||||
const auto supportMode = session && session->supportMode();
|
||||
|
||||
const auto &bg = muted ? st::trayCounterBgMute : st::trayCounterBg;
|
||||
const auto &fg = st::trayCounterFg;
|
||||
const auto counterArgs = [&](int size, int counter) {
|
||||
return Window::CounterLayerArgs{
|
||||
.size = size,
|
||||
.count = counter,
|
||||
.bg = bg,
|
||||
.fg = fg,
|
||||
};
|
||||
};
|
||||
const auto iconWithCounter = [&](int size, int counter, bool smallIcon) {
|
||||
return Ui::PixmapFromImage(IconWithCounter(
|
||||
counterArgs(size, counter),
|
||||
session,
|
||||
smallIcon));
|
||||
};
|
||||
|
||||
auto iconSmallPixmap16 = iconWithCounter(16, counter, true);
|
||||
auto iconSmallPixmap32 = iconWithCounter(32, counter, true);
|
||||
auto iconSmallPixmap16 = Tray::IconWithCounter(
|
||||
Tray::CounterLayerArgs(16, counter, muted),
|
||||
true,
|
||||
supportMode);
|
||||
auto iconSmallPixmap32 = Tray::IconWithCounter(
|
||||
Tray::CounterLayerArgs(32, counter, muted),
|
||||
true,
|
||||
supportMode);
|
||||
QIcon iconSmall, iconBig;
|
||||
iconSmall.addPixmap(iconSmallPixmap16);
|
||||
iconSmall.addPixmap(iconSmallPixmap32);
|
||||
const auto bigCounter = taskbarList.Get() ? 0 : counter;
|
||||
iconBig.addPixmap(iconWithCounter(32, bigCounter, false));
|
||||
iconBig.addPixmap(iconWithCounter(64, bigCounter, false));
|
||||
if (trayIcon) {
|
||||
// Force Qt to use right icon size, not the larger one.
|
||||
QIcon forTrayIcon;
|
||||
forTrayIcon.addPixmap(iconSizeSmall.width() >= 20
|
||||
? iconSmallPixmap32
|
||||
: iconSmallPixmap16);
|
||||
trayIcon->setIcon(forTrayIcon);
|
||||
}
|
||||
iconBig.addPixmap(Tray::IconWithCounter(
|
||||
Tray::CounterLayerArgs(32, bigCounter, muted),
|
||||
false,
|
||||
supportMode));
|
||||
iconBig.addPixmap(Tray::IconWithCounter(
|
||||
Tray::CounterLayerArgs(64, bigCounter, muted),
|
||||
false,
|
||||
supportMode));
|
||||
|
||||
destroyCachedIcons();
|
||||
_iconSmall = NativeIcon(iconSmall, iconSizeSmall);
|
||||
@@ -490,7 +369,7 @@ void MainWindow::updateIconCounters() {
|
||||
if (counter > 0) {
|
||||
const auto pixmap = [&](int size) {
|
||||
return Ui::PixmapFromImage(Window::GenerateCounterLayer(
|
||||
counterArgs(size, counter)));
|
||||
Tray::CounterLayerArgs(size, counter, muted)));
|
||||
};
|
||||
QIcon iconOverlay;
|
||||
iconOverlay.addPixmap(pixmap(16));
|
||||
@@ -624,17 +503,6 @@ void MainWindow::validateWindowTheme(bool native, bool night) {
|
||||
SendMessage(_hWnd, WM_NCACTIVATE, _hasActiveFrame ? 1 : 0, 0);
|
||||
}
|
||||
|
||||
void MainWindow::showFromTrayMenu() {
|
||||
// If we try to activate() window before the trayIconMenu is hidden,
|
||||
// then the window will be shown in semi-active state (Qt bug).
|
||||
// It will receive input events, but it will be rendered as inactive.
|
||||
using namespace rpl::mappers;
|
||||
_showFromTrayLifetime = trayIconMenu->shownValue(
|
||||
) | rpl::filter(!_1) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
showFromTray();
|
||||
});
|
||||
}
|
||||
|
||||
HWND MainWindow::psHwnd() const {
|
||||
return _hWnd;
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ class MainWindow : public Window::MainWindow {
|
||||
public:
|
||||
explicit MainWindow(not_null<Window::Controller*> controller);
|
||||
|
||||
void showFromTrayMenu() override;
|
||||
|
||||
HWND psHwnd() const;
|
||||
|
||||
void updateWindowIcon() override;
|
||||
@@ -34,8 +32,6 @@ public:
|
||||
|
||||
[[nodiscard]] bool hasTabletView() const;
|
||||
|
||||
void psShowTrayMenu();
|
||||
|
||||
void destroyedFromSystem();
|
||||
|
||||
~MainWindow();
|
||||
@@ -45,18 +41,6 @@ protected:
|
||||
int32 screenNameChecksum(const QString &name) const override;
|
||||
void unreadCounterChangedHook() override;
|
||||
|
||||
bool hasTrayIcon() const override {
|
||||
return trayIcon;
|
||||
}
|
||||
|
||||
QSystemTrayIcon *trayIcon = nullptr;
|
||||
Ui::PopupMenu *trayIconMenu = nullptr;
|
||||
|
||||
void psTrayMenuUpdated();
|
||||
void psSetupTrayIcon();
|
||||
|
||||
void showTrayTooltip() override;
|
||||
|
||||
void workmodeUpdated(Core::Settings::WorkMode mode) override;
|
||||
|
||||
bool initGeometryFromSystem() override;
|
||||
@@ -83,7 +67,6 @@ private:
|
||||
|
||||
// Workarounds for activation from tray icon.
|
||||
crl::time _lastDeactivateTime = 0;
|
||||
rpl::lifetime _showFromTrayLifetime;
|
||||
|
||||
bool _hasActiveFrame = false;
|
||||
|
||||
|
||||
258
Telegram/SourceFiles/platform/win/tray_win.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/win/tray_win.h"
|
||||
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
#include "core/application.h"
|
||||
#include "main/main_session.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_window.h"
|
||||
|
||||
#include <QtWidgets/QSystemTrayIcon>
|
||||
|
||||
namespace Platform {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kTooltipDelay = crl::time(10000);
|
||||
|
||||
[[nodiscard]] QImage ImageIconWithCounter(
|
||||
Window::CounterLayerArgs &&args,
|
||||
bool supportMode,
|
||||
bool smallIcon) {
|
||||
static constexpr auto kCount = 3;
|
||||
static auto ScaledLogo = std::array<QImage, kCount>();
|
||||
static auto ScaledLogoNoMargin = std::array<QImage, kCount>();
|
||||
|
||||
struct Dimensions {
|
||||
int index = 0;
|
||||
int size = 0;
|
||||
};
|
||||
const auto d = [&]() -> Dimensions {
|
||||
switch (args.size) {
|
||||
case 16:
|
||||
return {
|
||||
.index = 0,
|
||||
.size = 16,
|
||||
};
|
||||
case 32:
|
||||
return {
|
||||
.index = 1,
|
||||
.size = 32,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
.index = 2,
|
||||
.size = 64,
|
||||
};
|
||||
}
|
||||
}();
|
||||
Assert(d.index < kCount);
|
||||
|
||||
auto &scaled = smallIcon ? ScaledLogoNoMargin : ScaledLogo;
|
||||
auto result = [&] {
|
||||
auto &image = scaled[d.index];
|
||||
if (image.isNull()) {
|
||||
image = (smallIcon
|
||||
? Window::LogoNoMargin()
|
||||
: Window::Logo()).scaledToWidth(
|
||||
d.size,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
return image;
|
||||
}();
|
||||
if (supportMode) {
|
||||
Window::ConvertIconToBlack(result);
|
||||
}
|
||||
if (!args.count) {
|
||||
return result;
|
||||
} else if (smallIcon) {
|
||||
return Window::WithSmallCounter(std::move(result), std::move(args));
|
||||
}
|
||||
QPainter p(&result);
|
||||
const auto half = d.size / 2;
|
||||
args.size = half;
|
||||
p.drawPixmap(
|
||||
half,
|
||||
half,
|
||||
Ui::PixmapFromImage(Window::GenerateCounterLayer(std::move(args))));
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QWidget *Parent() {
|
||||
Expects(Core::App().primaryWindow() != nullptr);
|
||||
return Core::App().primaryWindow()->widget();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Tray::Tray() {
|
||||
}
|
||||
|
||||
void Tray::createIcon() {
|
||||
if (!_icon) {
|
||||
_icon = base::make_unique_q<QSystemTrayIcon>(Parent());
|
||||
updateIcon();
|
||||
_icon->setToolTip(AppName.utf16());
|
||||
using Reason = QSystemTrayIcon::ActivationReason;
|
||||
base::qt_signal_producer(
|
||||
_icon.get(),
|
||||
&QSystemTrayIcon::activated
|
||||
) | rpl::start_with_next([=](Reason reason) {
|
||||
if (reason == QSystemTrayIcon::Context && _menu) {
|
||||
_aboutToShowRequests.fire({});
|
||||
InvokeQueued(_menu.get(), [=] {
|
||||
_menu->popup(QCursor::pos());
|
||||
});
|
||||
} else {
|
||||
_iconClicks.fire({});
|
||||
}
|
||||
}, _lifetime);
|
||||
} else {
|
||||
updateIcon();
|
||||
}
|
||||
|
||||
_icon->show();
|
||||
}
|
||||
|
||||
void Tray::destroyIcon() {
|
||||
_icon = nullptr;
|
||||
}
|
||||
|
||||
void Tray::updateIcon() {
|
||||
if (!_icon) {
|
||||
return;
|
||||
}
|
||||
const auto counter = Core::App().unreadBadge();
|
||||
const auto muted = Core::App().unreadBadgeMuted();
|
||||
const auto controller = Core::App().primaryWindow();
|
||||
const auto session = !controller
|
||||
? nullptr
|
||||
: !controller->sessionController()
|
||||
? nullptr
|
||||
: &controller->sessionController()->session();
|
||||
const auto supportMode = session && session->supportMode();
|
||||
const auto iconSizeWidth = GetSystemMetrics(SM_CXSMICON);
|
||||
|
||||
auto iconSmallPixmap16 = Tray::IconWithCounter(
|
||||
CounterLayerArgs(16, counter, muted),
|
||||
true,
|
||||
supportMode);
|
||||
auto iconSmallPixmap32 = Tray::IconWithCounter(
|
||||
CounterLayerArgs(32, counter, muted),
|
||||
true,
|
||||
supportMode);
|
||||
auto iconSmall = QIcon();
|
||||
iconSmall.addPixmap(iconSmallPixmap16);
|
||||
iconSmall.addPixmap(iconSmallPixmap32);
|
||||
// Force Qt to use right icon size, not the larger one.
|
||||
QIcon forTrayIcon;
|
||||
forTrayIcon.addPixmap(iconSizeWidth >= 20
|
||||
? iconSmallPixmap32
|
||||
: iconSmallPixmap16);
|
||||
_icon->setIcon(forTrayIcon);
|
||||
}
|
||||
|
||||
void Tray::createMenu() {
|
||||
if (!_menu) {
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(nullptr);
|
||||
_menu->deleteOnHide(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Tray::destroyMenu() {
|
||||
_menu = nullptr;
|
||||
_actionsLifetime.destroy();
|
||||
}
|
||||
|
||||
void Tray::addAction(rpl::producer<QString> text, Fn<void()> &&callback) {
|
||||
if (!_menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we try to activate() window before the _menu is hidden,
|
||||
// then the window will be shown in semi-active state (Qt bug).
|
||||
// It will receive input events, but it will be rendered as inactive.
|
||||
auto callbackLater = crl::guard(_menu.get(), [=] {
|
||||
using namespace rpl::mappers;
|
||||
_callbackFromTrayLifetime = _menu->shownValue(
|
||||
) | rpl::filter(!_1) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
const auto action = _menu->addAction(QString(), std::move(callbackLater));
|
||||
std::move(
|
||||
text
|
||||
) | rpl::start_with_next([=](const QString &text) {
|
||||
action->setText(text);
|
||||
}, _actionsLifetime);
|
||||
}
|
||||
|
||||
void Tray::showTrayMessage() const {
|
||||
if (!cSeenTrayTooltip() && _icon) {
|
||||
_icon->showMessage(
|
||||
AppName.utf16(),
|
||||
tr::lng_tray_icon_text(tr::now),
|
||||
QSystemTrayIcon::Information,
|
||||
kTooltipDelay);
|
||||
cSetSeenTrayTooltip(true);
|
||||
Local::writeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
bool Tray::hasTrayMessageSupport() const {
|
||||
return !cSeenTrayTooltip();
|
||||
}
|
||||
|
||||
rpl::producer<> Tray::aboutToShowRequests() const {
|
||||
return _aboutToShowRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<> Tray::showFromTrayRequests() const {
|
||||
return rpl::never<>();
|
||||
}
|
||||
|
||||
rpl::producer<> Tray::hideToTrayRequests() const {
|
||||
return rpl::never<>();
|
||||
}
|
||||
|
||||
rpl::producer<> Tray::iconClicks() const {
|
||||
return _iconClicks.events();
|
||||
}
|
||||
|
||||
rpl::lifetime &Tray::lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
Window::CounterLayerArgs Tray::CounterLayerArgs(
|
||||
int size,
|
||||
int counter,
|
||||
bool muted) {
|
||||
return Window::CounterLayerArgs{
|
||||
.size = size,
|
||||
.count = counter,
|
||||
.bg = muted ? st::trayCounterBgMute : st::trayCounterBg,
|
||||
.fg = st::trayCounterFg,
|
||||
};
|
||||
}
|
||||
|
||||
QPixmap Tray::IconWithCounter(
|
||||
Window::CounterLayerArgs &&args,
|
||||
bool smallIcon,
|
||||
bool supportMode) {
|
||||
return Ui::PixmapFromImage(
|
||||
ImageIconWithCounter(std::move(args), supportMode, smallIcon));
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
73
Telegram/SourceFiles/platform/win/tray_win.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "platform/platform_tray.h"
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
namespace Window {
|
||||
class CounterLayerArgs;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
class QSystemTrayIcon;
|
||||
|
||||
namespace Platform {
|
||||
|
||||
class Tray final {
|
||||
public:
|
||||
Tray();
|
||||
|
||||
[[nodiscard]] rpl::producer<> aboutToShowRequests() const;
|
||||
[[nodiscard]] rpl::producer<> showFromTrayRequests() const;
|
||||
[[nodiscard]] rpl::producer<> hideToTrayRequests() const;
|
||||
[[nodiscard]] rpl::producer<> iconClicks() const;
|
||||
|
||||
void createIcon();
|
||||
void destroyIcon();
|
||||
|
||||
void updateIcon();
|
||||
|
||||
void createMenu();
|
||||
void destroyMenu();
|
||||
|
||||
void addAction(rpl::producer<QString> text, Fn<void()> &&callback);
|
||||
|
||||
void showTrayMessage() const;
|
||||
[[nodiscard]] bool hasTrayMessageSupport() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
// Windows only.
|
||||
[[nodiscard]] static Window::CounterLayerArgs CounterLayerArgs(
|
||||
int size,
|
||||
int counter,
|
||||
bool muted);
|
||||
[[nodiscard]] static QPixmap IconWithCounter(
|
||||
Window::CounterLayerArgs &&args,
|
||||
bool smallIcon,
|
||||
bool supportMode);
|
||||
|
||||
private:
|
||||
base::unique_qptr<QSystemTrayIcon> _icon;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
|
||||
rpl::event_stream<> _iconClicks;
|
||||
rpl::event_stream<> _aboutToShowRequests;
|
||||
|
||||
rpl::lifetime _callbackFromTrayLifetime;
|
||||
rpl::lifetime _actionsLifetime;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Platform
|
||||
@@ -18,6 +18,8 @@ settingsButton: SettingsButton(infoProfileButton) {
|
||||
settingsButtonNoIcon: SettingsButton(settingsButton) {
|
||||
padding: margins(22px, 10px, 22px, 8px);
|
||||
}
|
||||
settingsButtonActive: SettingsButton(infoMainButton, settingsButton) {
|
||||
}
|
||||
settingsAttentionButton: SettingsButton(settingsButtonNoIcon) {
|
||||
textFg: attentionButtonFg;
|
||||
textFgOver: attentionButtonFgOver;
|
||||
@@ -91,6 +93,9 @@ settingsIconPin: icon {{ "settings/pin", settingsIconFg }};
|
||||
settingsIconDownload: icon {{ "settings/download", settingsIconFg }};
|
||||
settingsIconMention: icon {{ "settings/mention", settingsIconFg }};
|
||||
|
||||
settingsIconAdd: icon {{ "settings/add", windowFgActive }};
|
||||
settingsIconRemove: icon {{ "settings/remove", windowFgActive }};
|
||||
|
||||
settingsCheckbox: Checkbox(defaultBoxCheckbox) {
|
||||
textPosition: point(15px, 1px);
|
||||
}
|
||||
@@ -247,8 +252,14 @@ settingsAccentColorSize: 24px;
|
||||
settingsAccentColorSkip: 4px;
|
||||
settingsAccentColorLine: 3px;
|
||||
|
||||
settingsFilterIconSkip: 68px;
|
||||
settingsFilterIconLeft: 17px;
|
||||
settingsFilterDividerLabel: FlatLabel(boxDividerLabel) {
|
||||
minWidth: 258px;
|
||||
maxHeight: 0px;
|
||||
align: align(top);
|
||||
}
|
||||
settingsFilterDividerLabelPadding: margins(0px, 16px, 0px, 22px);
|
||||
settingsFilterIconSize: 74px;
|
||||
settingsFilterIconPadding: margins(0px, 17px, 0px, 5px);
|
||||
|
||||
settingsDeviceName: InputField(defaultInputField) {
|
||||
textBg: transparent;
|
||||
|
||||
215
Telegram/SourceFiles/settings/settings_blocked_peers.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "settings/settings_blocked_peers.h"
|
||||
|
||||
#include "api/api_blocked_peers.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_privacy_controllers.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace Settings {
|
||||
|
||||
Blocked::Blocked(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
: Section(parent)
|
||||
, _controller(controller) {
|
||||
|
||||
setupContent();
|
||||
|
||||
{
|
||||
auto padding = st::changePhoneIconPadding;
|
||||
padding.setBottom(padding.top());
|
||||
_loading = base::make_unique_q<Ui::CenterWrap<>>(
|
||||
this,
|
||||
object_ptr<Ui::PaddingWrap<>>(
|
||||
this,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
this,
|
||||
tr::lng_contacts_loading(),
|
||||
st::changePhoneDescription),
|
||||
std::move(padding)));
|
||||
Ui::ResizeFitChild(this, _loading.get());
|
||||
}
|
||||
|
||||
_controller->session().api().blockedPeers().slice(
|
||||
) | rpl::start_with_next([=](const Api::BlockedPeers::Slice &slice) {
|
||||
checkTotal(slice.total);
|
||||
}, lifetime());
|
||||
|
||||
_controller->session().changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::IsBlocked
|
||||
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||
if (update.peer->isBlocked()) {
|
||||
checkTotal(1);
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
rpl::producer<QString> Blocked::title() {
|
||||
return tr::lng_settings_blocked_users();
|
||||
}
|
||||
|
||||
QPointer<Ui::RpWidget> Blocked::createPinnedToTop(not_null<QWidget*> parent) {
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(parent.get());
|
||||
|
||||
AddSkip(content);
|
||||
|
||||
AddButton(
|
||||
content,
|
||||
tr::lng_blocked_list_add(),
|
||||
st::settingsButtonActive,
|
||||
{ &st::menuIconBlockSettings, 0, IconType::Round, &st::transparent }
|
||||
)->addClickHandler([=] {
|
||||
BlockedBoxController::BlockNewPeer(_controller);
|
||||
});
|
||||
|
||||
AddSkip(content);
|
||||
AddDividerText(content, tr::lng_blocked_list_about());
|
||||
|
||||
{
|
||||
const auto subtitle = content->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
content,
|
||||
object_ptr<Ui::VerticalLayout>(content)))->setDuration(0);
|
||||
AddSkip(subtitle->entity());
|
||||
auto subtitleText = _countBlocked.value(
|
||||
) | rpl::map([=](int count) {
|
||||
return tr::lng_blocked_list_subtitle(tr::now, lt_count, count);
|
||||
});
|
||||
AddSubsectionTitle(
|
||||
subtitle->entity(),
|
||||
rpl::duplicate(subtitleText),
|
||||
st::blockedUsersListSubtitleAddPadding);
|
||||
subtitle->toggleOn(
|
||||
rpl::merge(
|
||||
_emptinessChanges.events() | rpl::map(!rpl::mappers::_1),
|
||||
_countBlocked.value() | rpl::map(rpl::mappers::_1 > 0)
|
||||
) | rpl::distinct_until_changed());
|
||||
|
||||
// Workaround.
|
||||
std::move(
|
||||
subtitleText
|
||||
) | rpl::start_with_next([=] {
|
||||
subtitle->entity()->resizeToWidth(content->width());
|
||||
}, subtitle->lifetime());
|
||||
}
|
||||
|
||||
return Ui::MakeWeak(not_null<Ui::RpWidget*>{ content });
|
||||
}
|
||||
|
||||
void Blocked::setupContent() {
|
||||
using namespace rpl::mappers;
|
||||
const auto container = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
|
||||
const auto listWrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
listWrap->toggleOn(
|
||||
_emptinessChanges.events_starting_with(true) | rpl::map(!_1),
|
||||
anim::type::instant);
|
||||
|
||||
{
|
||||
struct State {
|
||||
std::unique_ptr<BlockedBoxController> controller;
|
||||
std::unique_ptr<PeerListContentDelegateSimple> delegate;
|
||||
};
|
||||
|
||||
auto controller = std::make_unique<BlockedBoxController>(_controller);
|
||||
const auto content = listWrap->entity()->add(
|
||||
object_ptr<PeerListContent>(this, controller.get()));
|
||||
|
||||
const auto state = content->lifetime().make_state<State>();
|
||||
state->controller = std::move(controller);
|
||||
state->delegate = std::make_unique<PeerListContentDelegateSimple>();
|
||||
|
||||
state->delegate->setContent(content);
|
||||
state->controller->setDelegate(state->delegate.get());
|
||||
|
||||
state->controller->rowsCountChanges(
|
||||
) | rpl::start_with_next([=](int total) {
|
||||
_countBlocked = total;
|
||||
checkTotal(total);
|
||||
}, content->lifetime());
|
||||
_countBlocked = content->fullRowsCount();
|
||||
}
|
||||
|
||||
const auto emptyWrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
emptyWrap->toggleOn(
|
||||
_emptinessChanges.events_starting_with(false),
|
||||
anim::type::instant);
|
||||
|
||||
{
|
||||
const auto content = emptyWrap->entity();
|
||||
auto icon = CreateLottieIcon(
|
||||
content,
|
||||
{
|
||||
.name = u"blocked_peers_empty"_q,
|
||||
.sizeOverride = {
|
||||
st::changePhoneIconSize,
|
||||
st::changePhoneIconSize,
|
||||
},
|
||||
},
|
||||
st::blockedUsersListIconPadding);
|
||||
content->add(std::move(icon.widget));
|
||||
|
||||
_showFinished.events(
|
||||
) | rpl::start_with_next([animate = std::move(icon.animate)] {
|
||||
animate(anim::repeat::once);
|
||||
}, content->lifetime());
|
||||
|
||||
content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
tr::lng_blocked_list_empty_title(),
|
||||
st::changePhoneTitle)),
|
||||
st::changePhoneTitlePadding);
|
||||
|
||||
content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
tr::lng_blocked_list_empty_description(),
|
||||
st::changePhoneDescription)),
|
||||
st::changePhoneDescriptionPadding);
|
||||
|
||||
AddSkip(content, st::blockedUsersListIconPadding.top());
|
||||
}
|
||||
|
||||
Ui::ResizeFitChild(this, container);
|
||||
}
|
||||
|
||||
void Blocked::checkTotal(int total) {
|
||||
_loading = nullptr;
|
||||
_emptinessChanges.fire(total <= 0);
|
||||
}
|
||||
|
||||
void Blocked::showFinished() {
|
||||
_showFinished.fire({});
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
46
Telegram/SourceFiles/settings/settings_blocked_peers.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "settings/settings_common.h"
|
||||
|
||||
namespace Window {
|
||||
class Controller;
|
||||
} // namespace Window
|
||||
|
||||
namespace Settings {
|
||||
|
||||
class Blocked : public Section<Blocked> {
|
||||
public:
|
||||
Blocked(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller);
|
||||
|
||||
void showFinished() override;
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
|
||||
[[nodiscard]] QPointer<Ui::RpWidget> createPinnedToTop(
|
||||
not_null<QWidget*> parent) override;
|
||||
|
||||
private:
|
||||
void setupContent();
|
||||
void checkTotal(int total);
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
|
||||
base::unique_qptr<Ui::RpWidget> _loading;
|
||||
|
||||
rpl::variable<int> _countBlocked;
|
||||
|
||||
rpl::event_stream<> _showFinished;
|
||||
rpl::event_stream<bool> _emptinessChanges;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Settings
|
||||
@@ -262,17 +262,22 @@ LottieIcon CreateLottieIcon(
|
||||
const auto icon = owned.get();
|
||||
|
||||
raw->lifetime().add([kept = std::move(owned)]{});
|
||||
const auto looped = raw->lifetime().make_state<bool>(true);
|
||||
|
||||
const auto animate = [=] {
|
||||
icon->animate([=] { raw->update(); }, 0, icon->framesCount());
|
||||
const auto start = [=] {
|
||||
icon->animate([=] { raw->update(); }, 0, icon->framesCount() - 1);
|
||||
};
|
||||
const auto animate = [=](anim::repeat repeat) {
|
||||
*looped = (repeat == anim::repeat::loop);
|
||||
start();
|
||||
};
|
||||
raw->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(raw);
|
||||
const auto left = (raw->width() - width) / 2;
|
||||
icon->paint(p, left, padding.top());
|
||||
if (!icon->animating() && icon->frameIndex() > 0) {
|
||||
animate();
|
||||
if (!icon->animating() && icon->frameIndex() > 0 && *looped) {
|
||||
start();
|
||||
}
|
||||
|
||||
}, raw->lifetime());
|
||||
|
||||