Compare commits
112 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ed56bb4e4 | ||
|
|
3793f7c3c9 | ||
|
|
0d1b778612 | ||
|
|
b919a0627a | ||
|
|
6374d4eeda | ||
|
|
3967052375 | ||
|
|
89ccc95023 | ||
|
|
24f2ca7443 | ||
|
|
f90e13f8b1 | ||
|
|
606f5377d5 | ||
|
|
c698327b24 | ||
|
|
655731741c | ||
|
|
d5cdb5582b | ||
|
|
5cb081ca9a | ||
|
|
f1e0b36f61 | ||
|
|
ea9813825d | ||
|
|
36b6f70613 | ||
|
|
5e60b87cf9 | ||
|
|
ada22ee6cc | ||
|
|
bb016e1489 | ||
|
|
b115ea74d0 | ||
|
|
1008774aef | ||
|
|
73018ff958 | ||
|
|
e799fdaa3d | ||
|
|
7656a546b0 | ||
|
|
57f9ae4b2a | ||
|
|
cbdd86d398 | ||
|
|
2fe2105a5f | ||
|
|
a986d7a3d6 | ||
|
|
690c5df87c | ||
|
|
1e2759840d | ||
|
|
bad888496c | ||
|
|
4348ddf938 | ||
|
|
894d6028bd | ||
|
|
e8edbb16ae | ||
|
|
a0a71687e7 | ||
|
|
d042963a47 | ||
|
|
64b12bde55 | ||
|
|
49736cd879 | ||
|
|
e55581e0c9 | ||
|
|
574d915c23 | ||
|
|
2616659116 | ||
|
|
3d1f21bd05 | ||
|
|
dc631ef631 | ||
|
|
5277080115 | ||
|
|
1ccfcc824c | ||
|
|
97e8c0956f | ||
|
|
03a7131a1a | ||
|
|
2d906bddb2 | ||
|
|
dd7598a701 | ||
|
|
b6f17e1cea | ||
|
|
eb42a77eb7 | ||
|
|
ad761011d6 | ||
|
|
3fadf2ee54 | ||
|
|
15254599e2 | ||
|
|
1607752cf9 | ||
|
|
cf0cde6e83 | ||
|
|
8fffe7d128 | ||
|
|
a483eb98a1 | ||
|
|
838a3b23c7 | ||
|
|
a030911ad5 | ||
|
|
8ae1b10b91 | ||
|
|
adc8d6a6d1 | ||
|
|
1704cb345a | ||
|
|
0b85d0f185 | ||
|
|
348dfefbaa | ||
|
|
7508980f62 | ||
|
|
e11efe483e | ||
|
|
b23e4fa491 | ||
|
|
b6b7f5706f | ||
|
|
613bf98283 | ||
|
|
0bdb38753b | ||
|
|
d557e0f2b7 | ||
|
|
e81f4e8545 | ||
|
|
3b7d5d3c80 | ||
|
|
0c37990ccd | ||
|
|
0fbea454bc | ||
|
|
d4d688d494 | ||
|
|
b3892f49fa | ||
|
|
daa3a2f62f | ||
|
|
5affb168a2 | ||
|
|
99af2a7058 | ||
|
|
b9acea9cef | ||
|
|
8fb6ece796 | ||
|
|
15a9842b9f | ||
|
|
b28da30038 | ||
|
|
8ce0bd5575 | ||
|
|
5d68d224e5 | ||
|
|
373635a765 | ||
|
|
0ecd4d3b40 | ||
|
|
3fd62d51aa | ||
|
|
818624e051 | ||
|
|
a6eb241ec1 | ||
|
|
1d7fb6c4ce | ||
|
|
2af63ec48f | ||
|
|
0709bc6d70 | ||
|
|
3a34881488 | ||
|
|
39f9147790 | ||
|
|
19a5dcbffc | ||
|
|
e864aa2ff2 | ||
|
|
fe85a8256a | ||
|
|
f24b0c6237 | ||
|
|
3940d57c3d | ||
|
|
8da33113a2 | ||
|
|
f66cfb5684 | ||
|
|
d648d294ca | ||
|
|
0c8033414e | ||
|
|
28f29b51c0 | ||
|
|
8142e83395 | ||
|
|
e247be7e33 | ||
|
|
e594b75f4c | ||
|
|
28f857f763 |
21
.github/lock.yml
vendored
Normal file
21
.github/lock.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 45
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
skipCreatedBefore: false
|
||||
|
||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||
exemptLabels: []
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: >
|
||||
This thread has been automatically locked since there has not been
|
||||
any recent activity after it was closed. Please open a new issue for
|
||||
related bugs.
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: true
|
||||
11
.github/no-response.yml
vendored
Normal file
11
.github/no-response.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# Number of days of inactivity before an Issue is closed for lack of response
|
||||
daysUntilClose: 30
|
||||
# Label requiring a response
|
||||
responseRequiredLabel: waiting for answer
|
||||
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue has been automatically closed because there has been no response
|
||||
to our request for more information from the original author. With only the
|
||||
information that is currently in the issue, we don't have enough information
|
||||
to take action. Please reach out if you have or find the answers we need so
|
||||
that we can investigate further.
|
||||
16
.github/workflows/copyright_year_updater.yml
vendored
Normal file
16
.github/workflows/copyright_year_updater.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Copyright year updater.
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: ["Restart copyright_year_updater workflow."]
|
||||
schedule:
|
||||
# At 03:00 on January 1.
|
||||
- cron: "0 3 1 1 *"
|
||||
|
||||
jobs:
|
||||
Copyright-year:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: desktop-app/action_code_updater@master
|
||||
with:
|
||||
type: "license-year"
|
||||
4
.github/workflows/mac.yml
vendored
4
.github/workflows/mac.yml
vendored
@@ -102,6 +102,8 @@ jobs:
|
||||
cd Libraries/macos
|
||||
echo "LibrariesPath=`pwd`" >> $GITHUB_ENV
|
||||
|
||||
curl -o tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
|
||||
|
||||
- name: Patches.
|
||||
run: |
|
||||
echo "Find necessary commit from doc."
|
||||
@@ -475,7 +477,7 @@ jobs:
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/tg_owt
|
||||
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}
|
||||
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }}
|
||||
- name: WebRTC.
|
||||
if: steps.cache-webrtc.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
|
||||
144
.github/workflows/user_agent_updater.yml
vendored
144
.github/workflows/user_agent_updater.yml
vendored
@@ -5,152 +5,14 @@ on:
|
||||
types: ["Restart user_agent_updater workflow."]
|
||||
schedule:
|
||||
# At 00:00 on day-of-month 1.
|
||||
- cron: '0 0 1 * *'
|
||||
- cron: "0 0 1 * *"
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
User-agent:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
codeFile: "Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp"
|
||||
headBranchPrefix: "chrome_"
|
||||
baseBranch: "dev"
|
||||
isPull: "0"
|
||||
|
||||
steps:
|
||||
- name: Set env.
|
||||
if: startsWith(github.event_name, 'pull_request')
|
||||
run: |
|
||||
echo "isPull=1" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up git.
|
||||
run: |
|
||||
token=${{ secrets.TOKEN_FOR_MASTER_UPDATER }}
|
||||
if [ -z "${token}" ]; then
|
||||
echo "Token is unset. Nothing to do."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
url=https://x-access-token:$token@github.com/$GITHUB_REPOSITORY
|
||||
|
||||
git config --global user.email "action@github.com"
|
||||
git config --global user.name "GitHub Action"
|
||||
|
||||
git remote set-url origin $url
|
||||
|
||||
- name: Delete branch.
|
||||
env:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
if: |
|
||||
env.isPull == '1'
|
||||
&& github.event.action == 'closed'
|
||||
&& startsWith(env.ref, env.headBranchPrefix)
|
||||
run: |
|
||||
git push origin --delete $ref
|
||||
|
||||
- name: Write a new version of Google Chrome to the user-agent for DNS.
|
||||
if: env.isPull == '0'
|
||||
shell: python
|
||||
run: |
|
||||
import subprocess, os, re;
|
||||
|
||||
regExpVersion = "[0-9]+.[0-9]+.[0-9]+.[0-9]+";
|
||||
chrome = "Chrome/";
|
||||
|
||||
def newVersion():
|
||||
output = subprocess.check_output(["google-chrome", "--version"]);
|
||||
version = re.search(regExpVersion, output);
|
||||
if not version:
|
||||
print("Can't find a Chrome version.");
|
||||
exit();
|
||||
return version.group(0);
|
||||
|
||||
newChromeVersion = newVersion();
|
||||
print(newChromeVersion);
|
||||
|
||||
def setEnv(value):
|
||||
open(os.environ['GITHUB_ENV'], "a").write(value);
|
||||
|
||||
def writeUserAgent():
|
||||
p = os.environ['codeFile'];
|
||||
w = open(p, "r");
|
||||
content = w.read();
|
||||
w.close();
|
||||
|
||||
regExpChrome = chrome + regExpVersion;
|
||||
|
||||
version = re.search(regExpChrome, content);
|
||||
if not version:
|
||||
print("Can't find an user-agent in the code.");
|
||||
exit();
|
||||
content = re.sub(regExpChrome, chrome + newChromeVersion, content);
|
||||
|
||||
w = open(p, "w");
|
||||
w.write(content);
|
||||
|
||||
setEnv("ChromeVersion=" + newChromeVersion);
|
||||
|
||||
writeUserAgent();
|
||||
|
||||
- name: Push to a new branch.
|
||||
if: env.isPull == '0' && env.ChromeVersion != ''
|
||||
run: |
|
||||
git diff > git_diff.txt
|
||||
if [[ ! -s git_diff.txt ]]; then
|
||||
echo "Nothing to commit."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git checkout -b $headBranchPrefix$ChromeVersion
|
||||
git add $codeFile
|
||||
git commit -m "Update User-Agent for DNS to Chrome $ChromeVersion."
|
||||
|
||||
git push origin $headBranchPrefix$ChromeVersion
|
||||
echo "Done!"
|
||||
|
||||
- name: Close previous pull requests.
|
||||
if: env.isPull == '0' && env.ChromeVersion != ''
|
||||
uses: actions/github-script@0.4.0
|
||||
- uses: desktop-app/action_code_updater@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const common = {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
};
|
||||
|
||||
github.pulls.list(common).then(response => {
|
||||
response.data.forEach((item, _) => {
|
||||
if (item.head.ref.startsWith(process.env.headBranchPrefix)) {
|
||||
console.log(`Close ${item.title} #${item.number}.`);
|
||||
github.pulls.update({
|
||||
pull_number: item.number,
|
||||
state: "closed",
|
||||
...common
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
- name: Create a new pull request.
|
||||
if: env.isPull == '0' && env.ChromeVersion != ''
|
||||
uses: actions/github-script@0.4.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const version = process.env.ChromeVersion;
|
||||
const title = `Update User-Agent for DNS to Chrome ${version}.`;
|
||||
|
||||
github.pulls.create({
|
||||
title: title,
|
||||
body: "",
|
||||
head: `${process.env.headBranchPrefix}${version}`,
|
||||
base: process.env.baseBranch,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
type: "user-agent"
|
||||
|
||||
3
.github/workflows/win.yml
vendored
3
.github/workflows/win.yml
vendored
@@ -103,6 +103,7 @@ jobs:
|
||||
- name: Generate cache key.
|
||||
shell: bash
|
||||
run: |
|
||||
curl -o $LibrariesPath/tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
|
||||
echo $MANUAL_CACHING >> CACHE_KEY.txt
|
||||
if [ "$AUTO_CACHING" == "1" ]; then
|
||||
thisFile=$REPO_NAME/.github/workflows/win.yml
|
||||
@@ -359,7 +360,7 @@ jobs:
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/tg_owt
|
||||
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}
|
||||
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }}
|
||||
- name: WebRTC.
|
||||
if: steps.cache-webrtc.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
|
||||
2
LEGAL
2
LEGAL
@@ -1,7 +1,7 @@
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
Copyright (c) 2014-2020 The Telegram Desktop Authors.
|
||||
Copyright (c) 2014-2021 The Telegram Desktop Authors.
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -17,13 +17,17 @@ The latest version is available for
|
||||
|
||||
* [Windows 7 and above](https://telegram.org/dl/desktop/win) ([portable](https://telegram.org/dl/desktop/win_portable))
|
||||
* [macOS 10.12 and above](https://telegram.org/dl/desktop/mac)
|
||||
* [OS X 10.10 and 10.11](https://telegram.org/dl/desktop/osx)
|
||||
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux) ([32 bit](https://telegram.org/dl/desktop/linux32))
|
||||
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux)
|
||||
* [Snap](https://snapcraft.io/telegram-desktop)
|
||||
* [Flatpak](https://flathub.org/apps/details/org.telegram.desktop)
|
||||
|
||||
## Old system versions
|
||||
|
||||
Version **2.4.4** was the last that supports older systems
|
||||
|
||||
* [OS X 10.10 and 10.11](https://updates.tdesktop.com/tosx/tsetup-osx.2.4.4.dmg)
|
||||
* [Linux static build for 32 bit](https://updates.tdesktop.com/tlinux32/tsetup32.2.4.4.tar.xz)
|
||||
|
||||
Version **1.8.15** was the last that supports older systems
|
||||
|
||||
* [Windows XP and Vista](https://updates.tdesktop.com/tsetup/tsetup.1.8.15.exe) ([portable](https://updates.tdesktop.com/tsetup/tportable.1.8.15.zip))
|
||||
|
||||
@@ -84,6 +84,13 @@ if (LINUX)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_kwayland
|
||||
)
|
||||
endif()
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED
|
||||
AND NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
|
||||
AND Qt5WaylandClient_VERSION VERSION_LESS 5.13.0)
|
||||
@@ -812,8 +819,17 @@ PRIVATE
|
||||
platform/linux/linux_desktop_environment.h
|
||||
platform/linux/linux_gdk_helper.cpp
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_libs.cpp
|
||||
platform/linux/linux_libs.h
|
||||
platform/linux/linux_gsd_media_keys.cpp
|
||||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/linux_gtk_file_dialog.cpp
|
||||
platform/linux/linux_gtk_file_dialog.h
|
||||
platform/linux/linux_gtk_integration_p.h
|
||||
platform/linux/linux_gtk_integration.cpp
|
||||
platform/linux/linux_gtk_integration.h
|
||||
platform/linux/linux_notification_service_watcher.cpp
|
||||
platform/linux/linux_notification_service_watcher.h
|
||||
platform/linux/linux_open_with_dialog.cpp
|
||||
platform/linux/linux_open_with_dialog.h
|
||||
platform/linux/linux_wayland_integration.cpp
|
||||
platform/linux/linux_wayland_integration.h
|
||||
platform/linux/linux_xlib_helper.cpp
|
||||
@@ -952,6 +968,8 @@ PRIVATE
|
||||
storage/storage_account.h
|
||||
storage/storage_cloud_blob.cpp
|
||||
storage/storage_cloud_blob.h
|
||||
storage/storage_cloud_song_cover.cpp
|
||||
storage/storage_cloud_song_cover.h
|
||||
storage/storage_domain.cpp
|
||||
storage/storage_domain.h
|
||||
storage/storage_facade.cpp
|
||||
@@ -1101,11 +1119,46 @@ if (NOT LINUX)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LINUX AND DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_gsd_media_keys.cpp
|
||||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/linux_notification_service_watcher.cpp
|
||||
platform/linux/linux_notification_service_watcher.h
|
||||
platform/linux/notifications_manager_linux.cpp
|
||||
)
|
||||
|
||||
nice_target_sources(Telegram ${src_loc}
|
||||
PRIVATE
|
||||
platform/linux/notifications_manager_linux_dummy.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LINUX AND DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc} platform/linux/linux_wayland_integration.cpp)
|
||||
nice_target_sources(Telegram ${src_loc} PRIVATE platform/linux/linux_wayland_integration_dummy.cpp)
|
||||
endif()
|
||||
|
||||
if (LINUX AND TDESKTOP_DISABLE_GTK_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_gdk_helper.cpp
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_gtk_file_dialog.cpp
|
||||
platform/linux/linux_gtk_file_dialog.h
|
||||
platform/linux/linux_gtk_integration_p.h
|
||||
platform/linux/linux_gtk_integration.cpp
|
||||
platform/linux/linux_open_with_dialog.cpp
|
||||
platform/linux/linux_open_with_dialog.h
|
||||
platform/linux/linux_xlib_helper.cpp
|
||||
platform/linux/linux_xlib_helper.h
|
||||
)
|
||||
|
||||
nice_target_sources(Telegram ${src_loc}
|
||||
PRIVATE
|
||||
platform/linux/linux_gtk_integration_dummy.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
nice_target_sources(Telegram ${src_loc} PRIVATE platform/mac/mac_iconv_helper.c)
|
||||
endif()
|
||||
|
||||
@@ -1187,6 +1187,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_in_reply_to" = "In reply to";
|
||||
"lng_edited" = "edited";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_sent_date" = "Sent: {date}";
|
||||
"lng_admin_badge" = "admin";
|
||||
"lng_owner_badge" = "owner";
|
||||
"lng_channel_badge" = "channel";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.5.2.0" />
|
||||
Version="2.5.6.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,5,2,0
|
||||
PRODUCTVERSION 2,5,2,0
|
||||
FILEVERSION 2,5,6,0
|
||||
PRODUCTVERSION 2,5,6,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "2.5.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
|
||||
VALUE "FileVersion", "2.5.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.5.2.0"
|
||||
VALUE "ProductVersion", "2.5.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,5,2,0
|
||||
PRODUCTVERSION 2,5,2,0
|
||||
FILEVERSION 2,5,6,0
|
||||
PRODUCTVERSION 2,5,6,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", "2.5.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
|
||||
VALUE "FileVersion", "2.5.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.5.2.0"
|
||||
VALUE "ProductVersion", "2.5.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "history/history_drag_area.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
|
||||
#include "platform/platform_specific.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "media/streaming/media_streaming_instance.h"
|
||||
@@ -171,7 +172,9 @@ EditCaptionBox::EditCaptionBox(
|
||||
_thumbw = 0;
|
||||
_thumbnailImageLoaded = true;
|
||||
} else {
|
||||
const auto thumbSize = st::msgFileThumbLayout.thumbSize;
|
||||
const auto thumbSize = (!media->document()->isSongWithCover()
|
||||
? st::msgFileThumbLayout
|
||||
: st::msgFileLayout).thumbSize;
|
||||
const auto tw = dimensions.width(), th = dimensions.height();
|
||||
if (tw > th) {
|
||||
_thumbw = (tw * thumbSize) / th;
|
||||
@@ -183,19 +186,31 @@ EditCaptionBox::EditCaptionBox(
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
const auto options = Images::Option::Smooth
|
||||
| Images::Option::RoundedSmall
|
||||
| Images::Option::RoundedTopLeft
|
||||
| Images::Option::RoundedTopRight
|
||||
| Images::Option::RoundedBottomLeft
|
||||
| Images::Option::RoundedBottomRight;
|
||||
_thumb = App::pixmapFromImageInPlace(Images::prepare(
|
||||
image->original(),
|
||||
_thumbw * cIntRetinaFactor(),
|
||||
0,
|
||||
options,
|
||||
thumbSize,
|
||||
thumbSize));
|
||||
if (media->document()->isSongWithCover()) {
|
||||
const auto size = QSize(thumbSize, thumbSize);
|
||||
_thumb = QPixmap(size);
|
||||
_thumb.fill(Qt::transparent);
|
||||
Painter p(&_thumb);
|
||||
|
||||
HistoryView::DrawThumbnailAsSongCover(
|
||||
p,
|
||||
_documentMedia,
|
||||
QRect(QPoint(), size));
|
||||
} else {
|
||||
const auto options = Images::Option::Smooth
|
||||
| Images::Option::RoundedSmall
|
||||
| Images::Option::RoundedTopLeft
|
||||
| Images::Option::RoundedTopRight
|
||||
| Images::Option::RoundedBottomLeft
|
||||
| Images::Option::RoundedBottomRight;
|
||||
_thumb = App::pixmapFromImageInPlace(Images::prepare(
|
||||
image->original(),
|
||||
_thumbw * cIntRetinaFactor(),
|
||||
0,
|
||||
options,
|
||||
thumbSize,
|
||||
thumbSize));
|
||||
}
|
||||
_thumbnailImageLoaded = true;
|
||||
};
|
||||
_refreshThumbnail();
|
||||
@@ -539,6 +554,14 @@ void EditCaptionBox::updateEditPreview() {
|
||||
song->title,
|
||||
song->performer);
|
||||
_isAudio = true;
|
||||
|
||||
if (auto cover = song->cover; !cover.isNull()) {
|
||||
_thumb = Ui::PrepareSongCoverForThumbnail(
|
||||
cover,
|
||||
st::msgFileLayout.thumbSize);
|
||||
_thumbw = _thumb.width() / cIntRetinaFactor();
|
||||
_thumbh = _thumb.height() / cIntRetinaFactor();
|
||||
}
|
||||
}
|
||||
|
||||
const auto getExt = [&] {
|
||||
@@ -810,15 +833,21 @@ void EditCaptionBox::setupDragArea() {
|
||||
areas.photo->setDroppedCallback(droppedCallback(true));
|
||||
}
|
||||
|
||||
bool EditCaptionBox::isThumbedLayout() const {
|
||||
return (_thumbw && !_isAudio);
|
||||
}
|
||||
|
||||
void EditCaptionBox::updateBoxSize() {
|
||||
auto newHeight = st::boxPhotoPadding.top() + st::boxPhotoCaptionSkip + _field->height() + errorTopSkip() + st::normalFont->height;
|
||||
if (_photo) {
|
||||
newHeight += _wayWrap->height() / 2;
|
||||
}
|
||||
const auto &st = _thumbw ? st::msgFileThumbLayout : st::msgFileLayout;
|
||||
const auto &st = isThumbedLayout()
|
||||
? st::msgFileThumbLayout
|
||||
: st::msgFileLayout;
|
||||
if (_photo || _animated) {
|
||||
newHeight += std::max(_thumbh, _gifh);
|
||||
} else if (_thumbw || _doc) {
|
||||
} else if (isThumbedLayout() || _doc) {
|
||||
newHeight += 0 + st.thumbSize + 0;
|
||||
} else {
|
||||
newHeight += st::boxTitleFont->height;
|
||||
@@ -902,7 +931,9 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
|
||||
icon->paintInCenter(p, inner);
|
||||
}
|
||||
} else if (_doc) {
|
||||
const auto &st = _thumbw ? st::msgFileThumbLayout : st::msgFileLayout;
|
||||
const auto &st = isThumbedLayout()
|
||||
? st::msgFileThumbLayout
|
||||
: st::msgFileLayout;
|
||||
const auto w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
|
||||
const auto h = 0 + st.thumbSize + 0;
|
||||
const auto nameleft = 0 + st.thumbSize + st.padding.right();
|
||||
@@ -918,18 +949,24 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
|
||||
// Ui::FillRoundCorner(p, x, y, w, h, st::msgInBg, Ui::MessageInCorners, &st::msgInShadow);
|
||||
|
||||
const auto rthumb = style::rtlrect(x + 0, y + 0, st.thumbSize, st.thumbSize, width());
|
||||
if (_thumbw) {
|
||||
if (isThumbedLayout()) {
|
||||
p.drawPixmap(rthumb.topLeft(), _thumb);
|
||||
} else {
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::msgFileInBg);
|
||||
|
||||
{
|
||||
if (_isAudio && _thumbw) {
|
||||
p.drawPixmap(rthumb.topLeft(), _thumb);
|
||||
} else {
|
||||
p.setBrush(st::msgFileInBg);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.drawEllipse(rthumb);
|
||||
}
|
||||
|
||||
const auto icon = &(_isAudio ? st::historyFileInPlay : _isImage ? st::historyFileInImage : st::historyFileInDocument);
|
||||
const auto icon = &(_isAudio
|
||||
? st::historyFileInPlay
|
||||
: _isImage
|
||||
? st::historyFileInImage
|
||||
: st::historyFileInDocument);
|
||||
icon->paintInCenter(p, rthumb);
|
||||
}
|
||||
p.setFont(st::semiboldFont);
|
||||
|
||||
@@ -96,6 +96,8 @@ private:
|
||||
void createEditMediaButton();
|
||||
bool setPreparedList(Ui::PreparedList &&list);
|
||||
|
||||
bool isThumbedLayout() const;
|
||||
|
||||
inline QString getNewMediaPath() {
|
||||
return _preparedList.files.empty()
|
||||
? QString()
|
||||
|
||||
@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "history/history.h"
|
||||
#include "dialogs/dialogs_main_list.h"
|
||||
#include "window/window_session_controller.h" // onShowAddContact()
|
||||
#include "window/window_session_controller.h" // showAddContact()
|
||||
#include "facades.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_profile.h"
|
||||
@@ -112,7 +112,7 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||
box->addLeftButton(
|
||||
tr::lng_profile_add_contact(),
|
||||
[=] { controller->widget()->onShowAddContact(); });
|
||||
[=] { controller->widget()->showAddContact(); });
|
||||
};
|
||||
return Box<PeerListBox>(
|
||||
std::make_unique<ContactsBoxController>(
|
||||
|
||||
@@ -358,7 +358,7 @@ bool ParticipantsAdditionalData::canRemoveUser(
|
||||
if (canRestrictUser(user)) {
|
||||
return true;
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
return chat->invitedByMe.contains(user);
|
||||
return !user->isSelf() && chat->invitedByMe.contains(user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1212,6 +1212,9 @@ void ParticipantsBoxController::rebuildChatAdmins(
|
||||
return true;
|
||||
}();
|
||||
if (same) {
|
||||
if (!_allLoaded && !delegate()->peerListFullRowsCount()) {
|
||||
chatListReady();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "calls/calls_panel.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "webrtc/webrtc_media_devices.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "facades.h"
|
||||
@@ -779,6 +780,8 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
sendSignalingData(bytes);
|
||||
});
|
||||
},
|
||||
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
|
||||
settings.callAudioBackend()),
|
||||
};
|
||||
if (Logs::DebugEnabled()) {
|
||||
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
|
||||
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "base/global_shortcuts.h"
|
||||
#include "webrtc/webrtc_media_devices.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
|
||||
#include <tgcalls/group/GroupInstanceImpl.h>
|
||||
|
||||
@@ -581,6 +582,8 @@ void GroupCall::createAndStartController() {
|
||||
},
|
||||
.initialInputDeviceId = _audioInputId.toStdString(),
|
||||
.initialOutputDeviceId = _audioOutputId.toStdString(),
|
||||
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
|
||||
settings.callAudioBackend()),
|
||||
};
|
||||
if (Logs::DebugEnabled()) {
|
||||
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
|
||||
|
||||
@@ -273,6 +273,8 @@ private:
|
||||
rpl::event_stream<MuteRequest> _toggleMuteRequests;
|
||||
rpl::event_stream<not_null<UserData*>> _kickMemberRequests;
|
||||
rpl::variable<int> _fullCount = 1;
|
||||
rpl::variable<int> _fullCountMin = 0;
|
||||
rpl::variable<int> _fullCountMax = std::numeric_limits<int>::max();
|
||||
|
||||
not_null<QWidget*> _menuParent;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
@@ -682,9 +684,12 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
_realCallRawValue = real;
|
||||
_realId = real->id();
|
||||
|
||||
_fullCount = real->fullCountValue(
|
||||
) | rpl::map([](int value) {
|
||||
return std::max(value, 1);
|
||||
_fullCount = rpl::combine(
|
||||
real->fullCountValue(),
|
||||
_fullCountMin.value(),
|
||||
_fullCountMax.value()
|
||||
) | rpl::map([](int value, int min, int max) {
|
||||
return std::max(std::clamp(value, min, max), 1);
|
||||
});
|
||||
|
||||
real->participantsSliceAdded(
|
||||
@@ -741,10 +746,14 @@ void MembersController::appendInvitedUsers() {
|
||||
void MembersController::updateRow(
|
||||
const std::optional<Data::GroupCall::Participant> &was,
|
||||
const Data::GroupCall::Participant &now) {
|
||||
auto countChange = 0;
|
||||
if (const auto row = findRow(now.user)) {
|
||||
if (now.speaking && (!was || !was->speaking)) {
|
||||
checkSpeakingRowPosition(row);
|
||||
}
|
||||
if (row->state() == Row::State::Invited) {
|
||||
countChange = 1;
|
||||
}
|
||||
updateRow(row, &now);
|
||||
} else if (auto row = createRow(now)) {
|
||||
if (row->speaking()) {
|
||||
@@ -767,6 +776,14 @@ void MembersController::updateRow(
|
||||
}
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
countChange = 1;
|
||||
}
|
||||
if (countChange) {
|
||||
const auto fullCountMin = _fullCountMin.current() + countChange;
|
||||
if (_fullCountMax.current() < fullCountMin) {
|
||||
_fullCountMax = fullCountMin;
|
||||
}
|
||||
_fullCountMin = fullCountMin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -879,10 +896,11 @@ void MembersController::prepare() {
|
||||
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
|
||||
|
||||
const auto call = _call.get();
|
||||
if (const auto real = _peer->groupCall();
|
||||
real && call && real->id() == call->id()) {
|
||||
if (const auto real = _peer->groupCall()
|
||||
; real && call && real->id() == call->id()) {
|
||||
prepareRows(real);
|
||||
} else if (auto row = createSelfRow()) {
|
||||
_fullCountMin = (row->state() == Row::State::Invited) ? 0 : 1;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
@@ -898,6 +916,7 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
auto foundSelf = false;
|
||||
auto changed = false;
|
||||
const auto &participants = real->participants();
|
||||
auto fullCountMin = 0;
|
||||
auto count = delegate()->peerListFullRowsCount();
|
||||
for (auto i = 0; i != count;) {
|
||||
auto row = delegate()->peerListRowAt(i);
|
||||
@@ -912,6 +931,7 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
not_null{ user },
|
||||
&Data::GroupCall::Participant::user);
|
||||
if (contains) {
|
||||
++fullCountMin;
|
||||
++i;
|
||||
} else {
|
||||
changed = true;
|
||||
@@ -927,18 +947,29 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
&Data::GroupCall::Participant::user);
|
||||
auto row = (i != end(participants)) ? createRow(*i) : createSelfRow();
|
||||
if (row) {
|
||||
if (row->state() != Row::State::Invited) {
|
||||
++fullCountMin;
|
||||
}
|
||||
changed = true;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
}
|
||||
for (const auto &participant : participants) {
|
||||
if (auto row = createRow(participant)) {
|
||||
++fullCountMin;
|
||||
changed = true;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
delegate()->peerListRefreshRows();
|
||||
if (_fullCountMax.current() < fullCountMin) {
|
||||
_fullCountMax = fullCountMin;
|
||||
}
|
||||
_fullCountMin = fullCountMin;
|
||||
if (real->participantsLoaded()) {
|
||||
_fullCountMax = fullCountMin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -480,6 +480,7 @@ void GroupCallSettingsBox(
|
||||
// Means we finished showing the box.
|
||||
crl::on_main(box, [=] {
|
||||
state->micTester = std::make_unique<Webrtc::AudioInputTester>(
|
||||
Core::App().settings().callAudioBackend(),
|
||||
Core::App().settings().callInputDeviceId());
|
||||
state->levelUpdateTimer.callEach(kMicTestUpdateInterval);
|
||||
});
|
||||
|
||||
@@ -1138,7 +1138,7 @@ void FieldAutocomplete::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
SendMenu::DefaultSilentCallback(send),
|
||||
SendMenu::DefaultScheduleCallback(this, type, send));
|
||||
|
||||
if (!_menu->actions().empty()) {
|
||||
if (!_menu->empty()) {
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,7 +594,7 @@ bool TabbedSelector::preventAutoHide() const {
|
||||
}
|
||||
|
||||
bool TabbedSelector::hasMenu() const {
|
||||
return (_menu && !_menu->actions().empty());
|
||||
return (_menu && !_menu->empty());
|
||||
}
|
||||
|
||||
QImage TabbedSelector::grabForAnimation() {
|
||||
@@ -877,18 +877,22 @@ void TabbedSelector::scrollToY(int y) {
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::contextMenuEvent(QContextMenuEvent *e) {
|
||||
void TabbedSelector::showMenuWithType(SendMenu::Type type) {
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(this);
|
||||
const auto type = _sendMenuType
|
||||
? _sendMenuType()
|
||||
: SendMenu::Type::Disabled;
|
||||
currentTab()->widget()->fillContextMenu(_menu, type);
|
||||
|
||||
if (!_menu->actions().empty()) {
|
||||
if (!_menu->empty()) {
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> TabbedSelector::contextMenuRequested() const {
|
||||
return events(
|
||||
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||
return e->type() == QEvent::ContextMenu;
|
||||
}) | rpl::to_empty;
|
||||
}
|
||||
|
||||
TabbedSelector::Inner::Inner(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
|
||||
@@ -82,6 +82,7 @@ public:
|
||||
rpl::producer<> cancelled() const;
|
||||
rpl::producer<> checkForHide() const;
|
||||
rpl::producer<> slideFinished() const;
|
||||
rpl::producer<> contextMenuRequested() const;
|
||||
|
||||
void setRoundRadius(int radius);
|
||||
void refreshStickers();
|
||||
@@ -109,9 +110,7 @@ public:
|
||||
_beforeHidingCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void setSendMenuType(Fn<SendMenu::Type()> &&callback) {
|
||||
_sendMenuType = std::move(callback);
|
||||
}
|
||||
void showMenuWithType(SendMenu::Type type);
|
||||
|
||||
// Float player interface.
|
||||
bool floatPlayerHandleWheelEvent(QEvent *e);
|
||||
@@ -127,7 +126,6 @@ public:
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
private:
|
||||
class Tab {
|
||||
@@ -228,8 +226,6 @@ private:
|
||||
Fn<void(SelectorTab)> _afterShownCallback;
|
||||
Fn<void(SelectorTab)> _beforeHidingCallback;
|
||||
|
||||
Fn<SendMenu::Type()> _sendMenuType;
|
||||
|
||||
rpl::event_stream<> _showRequests;
|
||||
rpl::event_stream<> _slideFinished;
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ std::map<int, const char*> BetaLogs() {
|
||||
"- Fix sticker pack opening.\n"
|
||||
|
||||
"- Fix group status display.\n"
|
||||
|
||||
|
||||
"- Fix group members display.\n"
|
||||
},
|
||||
{
|
||||
@@ -82,6 +82,37 @@ std::map<int, const char*> BetaLogs() {
|
||||
"- Fix possible crash in connecting to voice chats.\n"
|
||||
|
||||
"- Use different audio module code on Windows in calls.\n"
|
||||
},
|
||||
{
|
||||
2005003,
|
||||
"- Allow using mouse buttons in Push-to-Talk shortcut.\n"
|
||||
|
||||
"- Fix blurred thumbnails in Shared Links section.\n"
|
||||
},
|
||||
{
|
||||
2005004,
|
||||
"- Implement new audio module code for calls and voice chats.\n"
|
||||
|
||||
"- Allow retracting votes from polls in comments to channel posts.\n"
|
||||
|
||||
"- Show small voice chat button for empty voice chats.\n"
|
||||
|
||||
"- Fix media viewer updating when screen resolution is changed.\n"
|
||||
},
|
||||
{
|
||||
2005005,
|
||||
"- Fix recording of audio in voice chats.\n"
|
||||
|
||||
"- Fix media viewer zoom and crashing.\n"
|
||||
},
|
||||
{
|
||||
2005006,
|
||||
"- Press Up arrow to edit your last sent comment.\n"
|
||||
|
||||
"- Add more information to date tooltips "
|
||||
"in Recent Actions and channel comments.\n"
|
||||
|
||||
"- Bug and crash fixes.\n"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,12 +13,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/section_widget.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
#include "facades.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
Settings::Settings()
|
||||
: _sendSubmitWay(Ui::InputSubmitSettings::Enter)
|
||||
: _callAudioBackend(Webrtc::Backend::OpenAL)
|
||||
, _sendSubmitWay(Ui::InputSubmitSettings::Enter)
|
||||
, _floatPlayerColumn(Window::Column::Second)
|
||||
, _floatPlayerCorner(RectPart::TopRight)
|
||||
, _dialogsWidthRatio(DefaultDialogsWidthRatio()) {
|
||||
@@ -112,7 +114,8 @@ QByteArray Settings::serialize() const {
|
||||
<< qint32(_ipRevealWarning ? 1 : 0)
|
||||
<< qint32(_groupCallPushToTalk ? 1 : 0)
|
||||
<< _groupCallPushToTalkShortcut
|
||||
<< qint64(_groupCallPushToTalkDelay);
|
||||
<< qint64(_groupCallPushToTalkDelay)
|
||||
<< qint32(_callAudioBackend);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -183,6 +186,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
qint32 groupCallPushToTalk = _groupCallPushToTalk ? 1 : 0;
|
||||
QByteArray groupCallPushToTalkShortcut = _groupCallPushToTalkShortcut;
|
||||
qint64 groupCallPushToTalkDelay = _groupCallPushToTalkDelay;
|
||||
qint32 callAudioBackend = static_cast<qint32>(_callAudioBackend);
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -275,6 +279,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
>> groupCallPushToTalkShortcut
|
||||
>> groupCallPushToTalkDelay;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> callAudioBackend;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -369,6 +376,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
_groupCallPushToTalk = (groupCallPushToTalk == 1);
|
||||
_groupCallPushToTalkShortcut = groupCallPushToTalkShortcut;
|
||||
_groupCallPushToTalkDelay = groupCallPushToTalkDelay;
|
||||
auto uncheckedBackend = static_cast<Webrtc::Backend>(callAudioBackend);
|
||||
switch (uncheckedBackend) {
|
||||
case Webrtc::Backend::OpenAL:
|
||||
case Webrtc::Backend::ADM:
|
||||
case Webrtc::Backend::ADM2: _callAudioBackend = uncheckedBackend; break;
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::chatWide() const {
|
||||
|
||||
@@ -21,6 +21,10 @@ namespace Window {
|
||||
enum class Column;
|
||||
} // namespace Window
|
||||
|
||||
namespace Webrtc {
|
||||
enum class Backend;
|
||||
} // namespace Webrtc
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Settings final {
|
||||
@@ -217,6 +221,12 @@ public:
|
||||
void setCallAudioDuckingEnabled(bool value) {
|
||||
_callAudioDuckingEnabled = value;
|
||||
}
|
||||
[[nodiscard]] Webrtc::Backend callAudioBackend() const {
|
||||
return _callAudioBackend;
|
||||
}
|
||||
void setCallAudioBackend(Webrtc::Backend backend) {
|
||||
_callAudioBackend = backend;
|
||||
}
|
||||
[[nodiscard]] bool groupCallPushToTalk() const {
|
||||
return _groupCallPushToTalk;
|
||||
}
|
||||
@@ -531,13 +541,14 @@ private:
|
||||
int _callOutputVolume = 100;
|
||||
int _callInputVolume = 100;
|
||||
bool _callAudioDuckingEnabled = true;
|
||||
Webrtc::Backend _callAudioBackend = Webrtc::Backend();
|
||||
bool _groupCallPushToTalk = false;
|
||||
QByteArray _groupCallPushToTalkShortcut;
|
||||
crl::time _groupCallPushToTalkDelay = 20;
|
||||
Window::Theme::AccentColors _themesAccentColors;
|
||||
bool _lastSeenWarningSeen = false;
|
||||
Ui::SendFilesWay _sendFilesWay;
|
||||
Ui::InputSubmitSettings _sendSubmitWay;
|
||||
Ui::SendFilesWay _sendFilesWay = Ui::SendFilesWay();
|
||||
Ui::InputSubmitSettings _sendSubmitWay = Ui::InputSubmitSettings();
|
||||
base::flat_map<QString, QString> _soundOverrides;
|
||||
bool _exeLaunchWarning = true;
|
||||
bool _ipRevealWarning = true;
|
||||
@@ -553,8 +564,8 @@ private:
|
||||
rpl::variable<bool> _autoDownloadDictionaries = true;
|
||||
rpl::variable<bool> _mainMenuAccountsShown = true;
|
||||
bool _tabbedSelectorSectionEnabled = false; // per-window
|
||||
Window::Column _floatPlayerColumn; // per-window
|
||||
RectPart _floatPlayerCorner; // per-window
|
||||
Window::Column _floatPlayerColumn = Window::Column(); // per-window
|
||||
RectPart _floatPlayerCorner = RectPart(); // per-window
|
||||
bool _thirdSectionInfoEnabled = true; // per-window
|
||||
rpl::event_stream<bool> _thirdSectionInfoEnabledValue; // per-window
|
||||
int _thirdSectionExtendedBy = -1; // per-window
|
||||
|
||||
@@ -270,14 +270,10 @@ std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) {
|
||||
|
||||
Launcher::Launcher(
|
||||
int argc,
|
||||
char *argv[],
|
||||
const QString &deviceModel,
|
||||
const QString &systemVersion)
|
||||
char *argv[])
|
||||
: _argc(argc)
|
||||
, _argv(argv)
|
||||
, _baseIntegration(_argc, _argv)
|
||||
, _deviceModel(deviceModel)
|
||||
, _systemVersion(systemVersion) {
|
||||
, _baseIntegration(_argc, _argv) {
|
||||
base::Integration::Set(&_baseIntegration);
|
||||
}
|
||||
|
||||
@@ -328,6 +324,23 @@ int Launcher::exec() {
|
||||
// Must be started before Platform is started.
|
||||
Logs::start(this);
|
||||
|
||||
if (Logs::DebugEnabled()) {
|
||||
const auto openalLogPath = QDir::toNativeSeparators(
|
||||
cWorkingDir() + qsl("DebugLogs/last_openal_log.txt"));
|
||||
|
||||
qputenv("ALSOFT_LOGLEVEL", "3");
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
_wputenv_s(
|
||||
L"ALSOFT_LOGFILE",
|
||||
openalLogPath.toStdWString().c_str());
|
||||
#else // Q_OS_WIN
|
||||
qputenv(
|
||||
"ALSOFT_LOGFILE",
|
||||
QFile::encodeName(openalLogPath));
|
||||
#endif // !Q_OS_WIN
|
||||
}
|
||||
|
||||
// Must be started before Sandbox is created.
|
||||
Platform::start();
|
||||
Ui::DisableCustomScaling();
|
||||
@@ -419,14 +432,6 @@ void Launcher::prepareSettings() {
|
||||
processArguments();
|
||||
}
|
||||
|
||||
QString Launcher::deviceModel() const {
|
||||
return _deviceModel;
|
||||
}
|
||||
|
||||
QString Launcher::systemVersion() const {
|
||||
return _systemVersion;
|
||||
}
|
||||
|
||||
uint64 Launcher::installationTag() const {
|
||||
return InstallationTag;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@ class Launcher {
|
||||
public:
|
||||
Launcher(
|
||||
int argc,
|
||||
char *argv[],
|
||||
const QString &deviceModel,
|
||||
const QString &systemVersion);
|
||||
char *argv[]);
|
||||
|
||||
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
|
||||
|
||||
@@ -26,8 +24,6 @@ public:
|
||||
QString argumentsString() const;
|
||||
bool customWorkingDir() const;
|
||||
|
||||
QString deviceModel() const;
|
||||
QString systemVersion() const;
|
||||
uint64 installationTag() const;
|
||||
|
||||
bool checkPortableVersionFolder();
|
||||
@@ -67,9 +63,6 @@ private:
|
||||
QStringList _arguments;
|
||||
BaseIntegration _baseIntegration;
|
||||
|
||||
const QString _deviceModel;
|
||||
const QString _systemVersion;
|
||||
|
||||
bool _customWorkingDir = false;
|
||||
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/sandbox.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "storage/localstorage.h"
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/parse_helper.h"
|
||||
#include "facades.h"
|
||||
|
||||
|
||||
@@ -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 = 2005002;
|
||||
constexpr auto AppVersionStr = "2.5.2";
|
||||
constexpr auto AppVersion = 2005006;
|
||||
constexpr auto AppVersionStr = "2.5.6";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/media/history_view_gif.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "storage/cache/storage_cache_database.h"
|
||||
#include "storage/storage_cloud_song_cover.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
@@ -540,6 +541,13 @@ void DocumentData::setattributes(
|
||||
songData->duration = data.vduration().v;
|
||||
songData->title = qs(data.vtitle().value_or_empty());
|
||||
songData->performer = qs(data.vperformer().value_or_empty());
|
||||
|
||||
if (!hasThumbnail()
|
||||
&& !songData->title.isEmpty()
|
||||
&& !songData->performer.isEmpty()) {
|
||||
|
||||
Storage::CloudSongCover::LoadThumbnailFromExternal(this);
|
||||
}
|
||||
}
|
||||
}, [&](const MTPDdocumentAttributeFilename &data) {
|
||||
_filename = qs(data.vfile_name());
|
||||
@@ -1488,6 +1496,10 @@ bool DocumentData::isSong() const {
|
||||
return (type == SongDocument);
|
||||
}
|
||||
|
||||
bool DocumentData::isSongWithCover() const {
|
||||
return isSong() && hasThumbnail();
|
||||
}
|
||||
|
||||
bool DocumentData::isAudioFile() const {
|
||||
if (isVoiceMessage()) {
|
||||
return false;
|
||||
|
||||
@@ -139,6 +139,7 @@ public:
|
||||
[[nodiscard]] bool isVoiceMessage() const;
|
||||
[[nodiscard]] bool isVideoMessage() const;
|
||||
[[nodiscard]] bool isSong() const;
|
||||
[[nodiscard]] bool isSongWithCover() const;
|
||||
[[nodiscard]] bool isAudioFile() const;
|
||||
[[nodiscard]] bool isVideoFile() const;
|
||||
[[nodiscard]] bool isAnimation() const;
|
||||
|
||||
@@ -145,4 +145,20 @@ void Groups::refreshViews(const HistoryItemsList &items) {
|
||||
}
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> Groups::findItemToEdit(
|
||||
not_null<HistoryItem*> item) const {
|
||||
const auto group = find(item);
|
||||
if (!group) {
|
||||
return item;
|
||||
}
|
||||
const auto &list = group->items;
|
||||
const auto it = ranges::find_if(
|
||||
list,
|
||||
ranges::not_fn(&HistoryItem::emptyText));
|
||||
if (it == end(list)) {
|
||||
return list.front();
|
||||
}
|
||||
return (*it);
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
|
||||
const Group *find(not_null<HistoryItem*> item) const;
|
||||
|
||||
not_null<HistoryItem*> findItemToEdit(not_null<HistoryItem*> item) const;
|
||||
|
||||
private:
|
||||
HistoryItemsList::const_iterator findPositionForItem(
|
||||
const HistoryItemsList &group,
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_replies_list.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_service.h"
|
||||
@@ -626,4 +627,22 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) {
|
||||
return (list.size() == skipped);
|
||||
}
|
||||
|
||||
HistoryItem *RepliesList::lastEditableMessage() {
|
||||
const auto message = [&](MsgId msgId) {
|
||||
return _history->owner().message(_history->channelId(), msgId);
|
||||
};
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
auto proj = [&](MsgId msgId) {
|
||||
if (const auto item = message(msgId)) {
|
||||
return item->allowsEdit(now);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto it = ranges::find_if(_list, std::move(proj));
|
||||
return (it == end(_list))
|
||||
? nullptr
|
||||
: _history->owner().groups().findItemToEdit(message(*it)).get();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
|
||||
[[nodiscard]] rpl::producer<int> fullCount() const;
|
||||
|
||||
[[nodiscard]] HistoryItem *lastEditableMessage();
|
||||
|
||||
private:
|
||||
struct Viewer;
|
||||
|
||||
|
||||
@@ -543,7 +543,8 @@ int32 ScheduledMessages::countListHash(const List &list) const {
|
||||
return HashFinalize(hash);
|
||||
}
|
||||
|
||||
HistoryItem *ScheduledMessages::lastSentMessage(not_null<History*> history) {
|
||||
HistoryItem *ScheduledMessages::lastEditableMessage(
|
||||
not_null<History*> history) {
|
||||
const auto i = _data.find(history);
|
||||
if (i == end(_data)) {
|
||||
return nullptr;
|
||||
@@ -552,10 +553,16 @@ HistoryItem *ScheduledMessages::lastSentMessage(not_null<History*> history) {
|
||||
|
||||
sort(list);
|
||||
const auto items = ranges::view::reverse(list.items);
|
||||
const auto it = ranges::find_if(
|
||||
items,
|
||||
&HistoryItem::canBeEditedFromHistory);
|
||||
return (it == end(items)) ? nullptr : (*it).get();
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
auto proj = [&](const OwnedItem &item) {
|
||||
return item->allowsEdit(now);
|
||||
};
|
||||
|
||||
const auto it = ranges::find_if(items, std::move(proj));
|
||||
return (it == end(items))
|
||||
? nullptr
|
||||
: history->owner().groups().findItemToEdit((*it).get()).get();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -32,7 +32,8 @@ public:
|
||||
[[nodiscard]] HistoryItem *lookupItem(PeerId peer, MsgId msg) const;
|
||||
[[nodiscard]] HistoryItem *lookupItem(FullMsgId itemId) const;
|
||||
[[nodiscard]] int count(not_null<History*> history) const;
|
||||
[[nodiscard]] HistoryItem *lastSentMessage(not_null<History*> history);
|
||||
[[nodiscard]] HistoryItem *lastEditableMessage(
|
||||
not_null<History*> history);
|
||||
|
||||
void checkEntitiesAndUpdate(const MTPDmessage &data);
|
||||
void apply(const MTPDupdateNewScheduledMessage &update);
|
||||
|
||||
@@ -1841,7 +1841,7 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
|
||||
selectByMouse(globalPosition);
|
||||
}
|
||||
});
|
||||
if (_menu->actions().empty()) {
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
} else {
|
||||
_menu->popup(e->globalPos());
|
||||
@@ -2281,7 +2281,7 @@ void InnerWidget::refreshEmptyLabel() {
|
||||
resizeEmptyLabel();
|
||||
_empty->setClickHandlerFilter([=](const auto &...) {
|
||||
if (_emptyState == EmptyState::NoContacts) {
|
||||
App::wnd()->onShowAddContact();
|
||||
App::wnd()->showAddContact();
|
||||
} else if (_emptyState == EmptyState::EmptyFolder) {
|
||||
editOpenedFilter();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "mainwindow.h"
|
||||
#include "mainwidget.h"
|
||||
#include "core/application.h"
|
||||
@@ -509,8 +510,17 @@ QString InnerWidget::tooltipText() const {
|
||||
if (_mouseCursorState == CursorState::Date
|
||||
&& _mouseAction == MouseAction::None) {
|
||||
if (const auto view = App::hoveredItem()) {
|
||||
auto dateText = view->dateTime().toString(
|
||||
QLocale::system().dateTimeFormat(QLocale::LongFormat));
|
||||
const auto format = QLocale::system().dateTimeFormat(
|
||||
QLocale::LongFormat);
|
||||
auto dateText = HistoryView::DateTooltipText(view);
|
||||
|
||||
const auto sentIt = _itemDates.find(view->data());
|
||||
if (sentIt != end(_itemDates)) {
|
||||
dateText += '\n' + tr::lng_sent_date(
|
||||
tr::now,
|
||||
lt_date,
|
||||
base::unixtime::parse(sentIt->second).toString(format));
|
||||
}
|
||||
return dateText;
|
||||
}
|
||||
} else if (_mouseCursorState == CursorState::Forwarded
|
||||
@@ -556,7 +566,7 @@ bool InnerWidget::elementUnderCursor(
|
||||
}
|
||||
|
||||
crl::time InnerWidget::elementHighlightTime(
|
||||
not_null<const HistoryView::Element*> element) {
|
||||
not_null<const HistoryItem*> item) {
|
||||
return crl::time(0);
|
||||
}
|
||||
|
||||
@@ -722,7 +732,10 @@ void InnerWidget::addEvents(Direction direction, const QVector<MTPChannelAdminLo
|
||||
}
|
||||
|
||||
auto count = 0;
|
||||
const auto addOne = [&](OwnedItem item) {
|
||||
const auto addOne = [&](OwnedItem item, TimeId sentDate) {
|
||||
if (sentDate) {
|
||||
_itemDates.emplace(item->data(), sentDate);
|
||||
}
|
||||
_eventIds.emplace(id);
|
||||
_itemsByData.emplace(item->data(), item.get());
|
||||
addToItems.push_back(std::move(item));
|
||||
@@ -1177,7 +1190,7 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
}
|
||||
}
|
||||
|
||||
if (_menu->actions().empty()) {
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
} else {
|
||||
_menu->popup(e->globalPos());
|
||||
|
||||
@@ -97,7 +97,7 @@ public:
|
||||
bool elementUnderCursor(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
crl::time elementHighlightTime(
|
||||
not_null<const HistoryView::Element*> element) override;
|
||||
not_null<const HistoryItem*> item) override;
|
||||
bool elementInSelectionMode() override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const HistoryView::Element*> view,
|
||||
@@ -242,6 +242,7 @@ private:
|
||||
std::vector<OwnedItem> _items;
|
||||
std::set<uint64> _eventIds;
|
||||
std::map<not_null<const HistoryItem*>, not_null<Element*>> _itemsByData;
|
||||
base::flat_map<not_null<const HistoryItem*>, TimeId> _itemDates;
|
||||
base::flat_set<FullMsgId> _animatedStickersPlayed;
|
||||
base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
|
||||
@@ -45,6 +45,16 @@ TextWithEntities PrepareText(const QString &value, const QString &emptyValue) {
|
||||
return result;
|
||||
}
|
||||
|
||||
TimeId ExtractSentDate(const MTPMessage &message) {
|
||||
return message.match([&](const MTPDmessageEmpty &) {
|
||||
return 0;
|
||||
}, [&](const MTPDmessageService &data) {
|
||||
return data.vdate().v;
|
||||
}, [&](const MTPDmessage &data) {
|
||||
return data.vdate().v;
|
||||
});
|
||||
}
|
||||
|
||||
MTPMessage PrepareLogMessage(
|
||||
const MTPMessage &message,
|
||||
MsgId newId,
|
||||
@@ -380,7 +390,7 @@ void GenerateItems(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<History*> history,
|
||||
const MTPDchannelAdminLogEvent &event,
|
||||
Fn<void(OwnedItem item)> callback) {
|
||||
Fn<void(OwnedItem item, TimeId sentDate)> callback) {
|
||||
Expects(history->peer->isChannel());
|
||||
|
||||
const auto session = &history->session();
|
||||
@@ -389,8 +399,10 @@ void GenerateItems(
|
||||
const auto channel = history->peer->asChannel();
|
||||
const auto &action = event.vaction();
|
||||
const auto date = event.vdate().v;
|
||||
const auto addPart = [&](not_null<HistoryItem*> item) {
|
||||
return callback(OwnedItem(delegate, item));
|
||||
const auto addPart = [&](
|
||||
not_null<HistoryItem*> item,
|
||||
TimeId sentDate = 0) {
|
||||
return callback(OwnedItem(delegate, item), sentDate);
|
||||
};
|
||||
|
||||
using Flag = MTPDmessage::Flag;
|
||||
@@ -545,13 +557,15 @@ void GenerateItems(
|
||||
addSimpleServiceMessage(text);
|
||||
|
||||
auto detachExistingItem = false;
|
||||
addPart(history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
MTPDmessage_ClientFlag::f_admin_log_entry,
|
||||
detachExistingItem));
|
||||
addPart(
|
||||
history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
MTPDmessage_ClientFlag::f_admin_log_entry,
|
||||
detachExistingItem),
|
||||
ExtractSentDate(action.vmessage()));
|
||||
}, [&](const auto &) {
|
||||
auto text = tr::lng_admin_log_unpinned_message(tr::now, lt_from, fromLinkText);
|
||||
addSimpleServiceMessage(text);
|
||||
@@ -598,10 +612,15 @@ void GenerateItems(
|
||||
addSimpleServiceMessage(text);
|
||||
|
||||
auto detachExistingItem = false;
|
||||
addPart(history->createItem(
|
||||
PrepareLogMessage(action.vmessage(), history->nextNonHistoryEntryId(), date),
|
||||
MTPDmessage_ClientFlag::f_admin_log_entry,
|
||||
detachExistingItem));
|
||||
addPart(
|
||||
history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
MTPDmessage_ClientFlag::f_admin_log_entry,
|
||||
detachExistingItem),
|
||||
ExtractSentDate(action.vmessage()));
|
||||
};
|
||||
|
||||
auto createParticipantJoin = [&]() {
|
||||
@@ -740,10 +759,15 @@ void GenerateItems(
|
||||
addSimpleServiceMessage(text);
|
||||
|
||||
auto detachExistingItem = false;
|
||||
addPart(history->createItem(
|
||||
PrepareLogMessage(action.vmessage(), history->nextNonHistoryEntryId(), date),
|
||||
MTPDmessage_ClientFlag::f_admin_log_entry,
|
||||
detachExistingItem));
|
||||
addPart(
|
||||
history->createItem(
|
||||
PrepareLogMessage(
|
||||
action.vmessage(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
date),
|
||||
MTPDmessage_ClientFlag::f_admin_log_entry,
|
||||
detachExistingItem),
|
||||
ExtractSentDate(action.vmessage()));
|
||||
};
|
||||
|
||||
auto createChangeLinkedChat = [&](const MTPDchannelAdminLogEventActionChangeLinkedChat &action) {
|
||||
@@ -838,7 +862,7 @@ void GenerateItems(
|
||||
data.vparticipant().match([&](const MTPDgroupCallParticipant &data) {
|
||||
const auto user = history->owner().user(data.vuser_id().v);
|
||||
const auto userLink = user->createOpenLink();
|
||||
const auto userLinkText = textcmdLink(1, user->name);
|
||||
const auto userLinkText = textcmdLink(2, user->name);
|
||||
auto text = tr::lng_admin_log_muted_participant(
|
||||
tr::now,
|
||||
lt_from,
|
||||
@@ -862,7 +886,7 @@ void GenerateItems(
|
||||
data.vparticipant().match([&](const MTPDgroupCallParticipant &data) {
|
||||
const auto user = history->owner().user(data.vuser_id().v);
|
||||
const auto userLink = user->createOpenLink();
|
||||
const auto userLinkText = textcmdLink(1, user->name);
|
||||
const auto userLinkText = textcmdLink(2, user->name);
|
||||
auto text = tr::lng_admin_log_unmuted_participant(
|
||||
tr::now,
|
||||
lt_from,
|
||||
|
||||
@@ -22,7 +22,7 @@ void GenerateItems(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<History*> history,
|
||||
const MTPDchannelAdminLogEvent &event,
|
||||
Fn<void(OwnedItem item)> callback);
|
||||
Fn<void(OwnedItem item, TimeId sentDate)> callback);
|
||||
|
||||
// Smart pointer wrapper for HistoryItem* that destroys the owned item.
|
||||
class OwnedItem {
|
||||
|
||||
@@ -2704,15 +2704,16 @@ MsgId History::msgIdForRead() const {
|
||||
: result;
|
||||
}
|
||||
|
||||
HistoryItem *History::lastSentMessage() const {
|
||||
HistoryItem *History::lastEditableMessage() const {
|
||||
if (!loadedAtBottom()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto now = base::unixtime::now();
|
||||
for (const auto &block : ranges::view::reverse(blocks)) {
|
||||
for (const auto &message : ranges::view::reverse(block->messages)) {
|
||||
const auto item = message->data();
|
||||
if (item->canBeEditedFromHistory()) {
|
||||
return item;
|
||||
if (item->allowsEdit(now)) {
|
||||
return owner().groups().findItemToEdit(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ public:
|
||||
MsgId minMsgId() const;
|
||||
MsgId maxMsgId() const;
|
||||
MsgId msgIdForRead() const;
|
||||
HistoryItem *lastSentMessage() const;
|
||||
HistoryItem *lastEditableMessage() const;
|
||||
|
||||
void resizeToWidth(int newWidth);
|
||||
void forceFullResize();
|
||||
|
||||
@@ -1751,18 +1751,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
}
|
||||
if (const auto media = item->media()) {
|
||||
if (const auto poll = media->poll()) {
|
||||
if (!poll->closed()) {
|
||||
if (poll->voted() && !poll->quiz()) {
|
||||
_menu->addAction(tr::lng_polls_retract(tr::now), [=] {
|
||||
session->api().sendPollVotes(itemId, {});
|
||||
});
|
||||
}
|
||||
if (item->canStopPoll()) {
|
||||
_menu->addAction(tr::lng_polls_stop(tr::now), [=] {
|
||||
HistoryView::StopPoll(session, itemId);
|
||||
});
|
||||
}
|
||||
}
|
||||
HistoryView::AddPollActions(
|
||||
_menu,
|
||||
poll,
|
||||
item,
|
||||
HistoryView::Context::History);
|
||||
} else if (const auto contact = media->sharedContact()) {
|
||||
const auto phone = contact->phoneNumber;
|
||||
_menu->addAction(tr::lng_profile_copy_phone(tr::now), [=] {
|
||||
@@ -1858,7 +1851,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
}
|
||||
}
|
||||
|
||||
if (_menu->actions().empty()) {
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
} else {
|
||||
_menu->popup(e->globalPos());
|
||||
@@ -2520,9 +2513,9 @@ void HistoryInner::elementStartStickerLoop(
|
||||
_animatedStickersPlayed.emplace(view->data());
|
||||
}
|
||||
|
||||
crl::time HistoryInner::elementHighlightTime(not_null<const Element*> view) {
|
||||
const auto fullAnimMs = _controller->content()->highlightStartTime(
|
||||
view->data());
|
||||
crl::time HistoryInner::elementHighlightTime(
|
||||
not_null<const HistoryItem*> item) {
|
||||
const auto fullAnimMs = _controller->content()->highlightStartTime(item);
|
||||
if (fullAnimMs > 0) {
|
||||
const auto now = crl::now();
|
||||
if (fullAnimMs < now) {
|
||||
@@ -3320,40 +3313,7 @@ QString HistoryInner::tooltipText() const {
|
||||
if (_mouseCursorState == CursorState::Date
|
||||
&& _mouseAction == MouseAction::None) {
|
||||
if (const auto view = App::hoveredItem()) {
|
||||
auto dateText = view->dateTime().toString(
|
||||
QLocale::system().dateTimeFormat(QLocale::LongFormat));
|
||||
if (const auto editedDate = view->displayedEditDate()) {
|
||||
dateText += '\n' + tr::lng_edited_date(
|
||||
tr::now,
|
||||
lt_date,
|
||||
base::unixtime::parse(editedDate).toString(
|
||||
QLocale::system().dateTimeFormat(
|
||||
QLocale::LongFormat)));
|
||||
}
|
||||
if (const auto forwarded = view->data()->Get<HistoryMessageForwarded>()) {
|
||||
dateText += '\n' + tr::lng_forwarded_date(
|
||||
tr::now,
|
||||
lt_date,
|
||||
base::unixtime::parse(forwarded->originalDate).toString(
|
||||
QLocale::system().dateTimeFormat(
|
||||
QLocale::LongFormat)));
|
||||
if (const auto media = view->media()) {
|
||||
if (media->hidesForwardedInfo()) {
|
||||
dateText += "\n" + tr::lng_forwarded(
|
||||
tr::now,
|
||||
lt_user,
|
||||
(forwarded->originalSender
|
||||
? forwarded->originalSender->shortName()
|
||||
: forwarded->hiddenSenderInfo->firstName));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (const auto msgsigned = view->data()->Get<HistoryMessageSigned>()) {
|
||||
if (msgsigned->isElided && !msgsigned->isAnonymousRank) {
|
||||
dateText += '\n' + tr::lng_signed_author(tr::now, lt_user, msgsigned->author);
|
||||
}
|
||||
}
|
||||
return dateText;
|
||||
return HistoryView::DateTooltipText(view);
|
||||
}
|
||||
} else if (_mouseCursorState == CursorState::Forwarded
|
||||
&& _mouseAction == MouseAction::None) {
|
||||
@@ -3421,8 +3381,8 @@ not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
|
||||
return (App::hoveredItem() == view);
|
||||
}
|
||||
crl::time elementHighlightTime(
|
||||
not_null<const Element*> view) override {
|
||||
return Instance ? Instance->elementHighlightTime(view) : 0;
|
||||
not_null<const HistoryItem*> item) override {
|
||||
return Instance ? Instance->elementHighlightTime(item) : 0;
|
||||
}
|
||||
bool elementInSelectionMode() override {
|
||||
return Instance ? Instance->inSelectionMode() : false;
|
||||
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
int till) const;
|
||||
void elementStartStickerLoop(not_null<const Element*> view);
|
||||
[[nodiscard]] crl::time elementHighlightTime(
|
||||
not_null<const Element*> view);
|
||||
not_null<const HistoryItem*> item);
|
||||
void elementShowPollResults(
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context);
|
||||
|
||||
@@ -762,27 +762,6 @@ bool HistoryItem::canUpdateDate() const {
|
||||
return isScheduled();
|
||||
}
|
||||
|
||||
bool HistoryItem::canBeEditedFromHistory() const {
|
||||
// Skip if message is editing media.
|
||||
if (isEditingMedia()) {
|
||||
return false;
|
||||
}
|
||||
// Skip if message is video message or sticker.
|
||||
if (const auto m = media()) {
|
||||
// Skip only if media is not webpage.
|
||||
if (!m->webpage() && !m->allowsEditCaption()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ((IsServerMsgId(id) || isScheduled())
|
||||
&& !serviceMsg()
|
||||
&& (out() || history()->peer->isSelf())
|
||||
&& !Has<HistoryMessageForwarded>()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HistoryItem::sendFailed() {
|
||||
Expects(_clientFlags & MTPDmessage_ClientFlag::f_sending);
|
||||
Expects(!(_clientFlags & MTPDmessage_ClientFlag::f_failed));
|
||||
|
||||
@@ -390,8 +390,6 @@ public:
|
||||
void updateDate(TimeId newDate);
|
||||
[[nodiscard]] bool canUpdateDate() const;
|
||||
|
||||
[[nodiscard]] bool canBeEditedFromHistory() const;
|
||||
|
||||
virtual ~HistoryItem();
|
||||
|
||||
MsgId id;
|
||||
|
||||
@@ -413,23 +413,13 @@ ReplyMarkupClickHandler::ReplyMarkupClickHandler(
|
||||
|
||||
// Copy to clipboard support.
|
||||
QString ReplyMarkupClickHandler::copyToClipboardText() const {
|
||||
if (const auto button = getButton()) {
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
if (button->type == Type::Url || button->type == Type::Auth) {
|
||||
return QString::fromUtf8(button->data);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
const auto button = getUrlButton();
|
||||
return button ? QString::fromUtf8(button->data) : QString();
|
||||
}
|
||||
|
||||
QString ReplyMarkupClickHandler::copyToClipboardContextItemText() const {
|
||||
if (const auto button = getButton()) {
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
if (button->type == Type::Url || button->type == Type::Auth) {
|
||||
return tr::lng_context_copy_link(tr::now);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
const auto button = getUrlButton();
|
||||
return button ? tr::lng_context_copy_link(tr::now) : QString();
|
||||
}
|
||||
|
||||
// Finds the corresponding button in the items markup struct.
|
||||
@@ -440,6 +430,17 @@ const HistoryMessageMarkupButton *ReplyMarkupClickHandler::getButton() const {
|
||||
return HistoryMessageMarkupButton::Get(_owner, _itemId, _row, _column);
|
||||
}
|
||||
|
||||
auto ReplyMarkupClickHandler::getUrlButton() const
|
||||
-> const HistoryMessageMarkupButton* {
|
||||
if (const auto button = getButton()) {
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
if (button->type == Type::Url || button->type == Type::Auth) {
|
||||
return button;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ReplyMarkupClickHandler::onClickImpl() const {
|
||||
if (const auto item = _owner->message(_itemId)) {
|
||||
App::activateBotCommand(item, _row, _column);
|
||||
@@ -454,6 +455,19 @@ QString ReplyMarkupClickHandler::buttonText() const {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString ReplyMarkupClickHandler::tooltip() const {
|
||||
const auto button = getUrlButton();
|
||||
const auto url = button ? QString::fromUtf8(button->data) : QString();
|
||||
const auto text = _fullDisplayed ? QString() : buttonText();
|
||||
if (!url.isEmpty() && !text.isEmpty()) {
|
||||
return QString("%1\n\n%2").arg(text).arg(url);
|
||||
} else if (url.isEmpty() != text.isEmpty()) {
|
||||
return text + url;
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
ReplyKeyboard::Button::Button() = default;
|
||||
ReplyKeyboard::Button::Button(Button &&other) = default;
|
||||
ReplyKeyboard::Button &ReplyKeyboard::Button::operator=(
|
||||
|
||||
@@ -251,9 +251,7 @@ public:
|
||||
int column,
|
||||
FullMsgId context);
|
||||
|
||||
QString tooltip() const override {
|
||||
return _fullDisplayed ? QString() : buttonText();
|
||||
}
|
||||
QString tooltip() const override;
|
||||
|
||||
void setFullDisplayed(bool full) {
|
||||
_fullDisplayed = full;
|
||||
@@ -269,6 +267,8 @@ public:
|
||||
// than the one was used when constructing the handler, but not a big deal.
|
||||
const HistoryMessageMarkupButton *getButton() const;
|
||||
|
||||
const HistoryMessageMarkupButton *getUrlButton() const;
|
||||
|
||||
// We hold only FullMsgId, not HistoryItem*, because all click handlers
|
||||
// are activated async and the item may be already destroyed.
|
||||
void setMessageId(const FullMsgId &msgId) {
|
||||
|
||||
@@ -846,6 +846,11 @@ void HistoryWidget::initTabbedSelector() {
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
auto filter = rpl::filter([=] {
|
||||
return !isHidden();
|
||||
});
|
||||
using Selector = TabbedSelector;
|
||||
|
||||
selector->emojiChosen(
|
||||
) | rpl::filter([=] {
|
||||
return !isHidden() && !_field->isHidden();
|
||||
@@ -854,27 +859,24 @@ void HistoryWidget::initTabbedSelector() {
|
||||
}, lifetime());
|
||||
|
||||
selector->fileChosen(
|
||||
) | rpl::filter([=] {
|
||||
return !isHidden();
|
||||
}) | rpl::start_with_next([=](TabbedSelector::FileChosen data) {
|
||||
) | filter | rpl::start_with_next([=](Selector::FileChosen data) {
|
||||
sendExistingDocument(data.document, data.options);
|
||||
}, lifetime());
|
||||
|
||||
selector->photoChosen(
|
||||
) | rpl::filter([=] {
|
||||
return !isHidden();
|
||||
}) | rpl::start_with_next([=](TabbedSelector::PhotoChosen data) {
|
||||
) | filter | rpl::start_with_next([=](Selector::PhotoChosen data) {
|
||||
sendExistingPhoto(data.photo, data.options);
|
||||
}, lifetime());
|
||||
|
||||
selector->inlineResultChosen(
|
||||
) | rpl::filter([=] {
|
||||
return !isHidden();
|
||||
}) | rpl::start_with_next([=](TabbedSelector::InlineChosen data) {
|
||||
) | filter | rpl::start_with_next([=](Selector::InlineChosen data) {
|
||||
sendInlineResult(data);
|
||||
}, lifetime());
|
||||
|
||||
selector->setSendMenuType([=] { return sendMenuType(); });
|
||||
selector->contextMenuRequested(
|
||||
) | filter | rpl::start_with_next([=] {
|
||||
selector->showMenuWithType(sendMenuType());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void HistoryWidget::supportInitAutocomplete() {
|
||||
@@ -1045,11 +1047,6 @@ void HistoryWidget::scrollToAnimationCallback(
|
||||
|
||||
void HistoryWidget::enqueueMessageHighlight(
|
||||
not_null<HistoryView::Element*> view) {
|
||||
if (const auto group = session().data().groups().find(view->data())) {
|
||||
if (const auto leader = group->items.front()->mainView()) {
|
||||
view = leader;
|
||||
}
|
||||
}
|
||||
auto enqueueMessageId = [this](MsgId universalId) {
|
||||
if (_highlightQueue.empty() && !_highlightTimer.isActive()) {
|
||||
highlightMessage(universalId);
|
||||
@@ -1096,7 +1093,7 @@ void HistoryWidget::checkNextHighlight() {
|
||||
|
||||
void HistoryWidget::updateHighlightedMessage() {
|
||||
const auto item = getItemFromHistoryOrMigrated(_highlightedMessageId);
|
||||
const auto view = item ? item->mainView() : nullptr;
|
||||
auto view = item ? item->mainView() : nullptr;
|
||||
if (!view) {
|
||||
return stopMessageHighlight();
|
||||
}
|
||||
@@ -1105,6 +1102,11 @@ void HistoryWidget::updateHighlightedMessage() {
|
||||
return stopMessageHighlight();
|
||||
}
|
||||
|
||||
if (const auto group = session().data().groups().find(view->data())) {
|
||||
if (const auto leader = group->items.front()->mainView()) {
|
||||
view = leader;
|
||||
}
|
||||
}
|
||||
session().data().requestViewRepaint(view);
|
||||
}
|
||||
|
||||
@@ -1661,6 +1663,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||
_editMsgId = 0;
|
||||
_replyToId = readyToForward() ? 0 : _history->localDraft()->msgId;
|
||||
}
|
||||
updateCmdStartShown();
|
||||
updateControlsVisibility();
|
||||
updateControlsGeometry();
|
||||
refreshTopBarActiveChat();
|
||||
@@ -2228,11 +2231,7 @@ void HistoryWidget::updateControlsVisibility() {
|
||||
_botCommandStart->hide();
|
||||
} else {
|
||||
_botKeyboardShow->hide();
|
||||
if (_cmdStartShown) {
|
||||
_botCommandStart->show();
|
||||
} else {
|
||||
_botCommandStart->hide();
|
||||
}
|
||||
_botCommandStart->setVisible(_cmdStartShown);
|
||||
}
|
||||
}
|
||||
_attachToggle->show();
|
||||
@@ -3738,7 +3737,7 @@ void HistoryWidget::updateSendButtonType() {
|
||||
bool HistoryWidget::updateCmdStartShown() {
|
||||
bool cmdStartShown = false;
|
||||
if (_history && _peer && ((_peer->isChat() && _peer->asChat()->botStatus > 0) || (_peer->isMegagroup() && _peer->asChannel()->mgInfo->botStatus > 0) || (_peer->isUser() && _peer->asUser()->isBot()))) {
|
||||
if (!isBotStart() && !isBlocked() && !_keyboard->hasMarkup() && !_keyboard->forceReply()) {
|
||||
if (!isBotStart() && !isBlocked() && !_keyboard->hasMarkup() && !_keyboard->forceReply() && !_editMsgId) {
|
||||
if (!HasSendText(_field)) {
|
||||
cmdStartShown = true;
|
||||
}
|
||||
@@ -4081,8 +4080,8 @@ void HistoryWidget::checkFieldAutocomplete() {
|
||||
&& cRecentInlineBots().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (autocomplete.query[0] == '/'
|
||||
&& _peer->isUser()
|
||||
&& !_peer->asUser()->isBot()) {
|
||||
&& ((_peer->isUser() && !_peer->asUser()->isBot())
|
||||
|| _editMsgId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4845,7 +4844,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
|
||||
_tabbedSelectorToggle->show();
|
||||
_botKeyboardHide->hide();
|
||||
_botKeyboardShow->hide();
|
||||
_botCommandStart->show();
|
||||
_botCommandStart->setVisible(!_editMsgId);
|
||||
}
|
||||
_field->setMaxHeight(computeMaxFieldHeight());
|
||||
_kbShown = false;
|
||||
@@ -5023,10 +5022,9 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
|
||||
_scroll->keyPressEvent(e);
|
||||
} else if (e->key() == Qt::Key_Up && !commonModifiers) {
|
||||
const auto item = _history
|
||||
? _history->lastSentMessage()
|
||||
? _history->lastEditableMessage()
|
||||
: nullptr;
|
||||
if (item
|
||||
&& item->allowsEdit(base::unixtime::now())
|
||||
&& _field->empty()
|
||||
&& !_editMsgId
|
||||
&& !_replyToId) {
|
||||
@@ -5652,7 +5650,7 @@ void HistoryWidget::editMessage(FullMsgId itemId) {
|
||||
}
|
||||
|
||||
void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
|
||||
if (_voiceRecordBar->isListenState()) {
|
||||
if (_voiceRecordBar->isActive()) {
|
||||
Ui::show(Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1410,7 +1410,10 @@ void ComposeControls::initTabbedSelector() {
|
||||
selector->inlineResultChosen(
|
||||
) | rpl::start_to_stream(_inlineResultChosen, wrap->lifetime());
|
||||
|
||||
selector->setSendMenuType([=] { return sendMenuType(); });
|
||||
selector->contextMenuRequested(
|
||||
) | rpl::start_with_next([=] {
|
||||
selector->showMenuWithType(sendMenuType());
|
||||
}, wrap->lifetime());
|
||||
}
|
||||
|
||||
void ComposeControls::initSendButton() {
|
||||
@@ -1814,7 +1817,7 @@ void ComposeControls::editMessage(not_null<HistoryItem*> item) {
|
||||
Expects(_history != nullptr);
|
||||
Expects(draftKeyCurrent() != Data::DraftKey::None());
|
||||
|
||||
if (_voiceRecordBar && _voiceRecordBar->isListenState()) {
|
||||
if (_voiceRecordBar->isActive()) {
|
||||
Ui::show(Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -924,12 +924,9 @@ CancelButton::CancelButton(not_null<Ui::RpWidget*> parent, int height)
|
||||
|
||||
void CancelButton::init() {
|
||||
_showProgress.value(
|
||||
) | rpl::start_with_next([=](float64 progress) {
|
||||
const auto hasProgress = (progress > 0.);
|
||||
if (isHidden() == !hasProgress) {
|
||||
setVisible(hasProgress);
|
||||
}
|
||||
update();
|
||||
) | rpl::map(rpl::mappers::_1 > 0.) | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=](bool hasProgress) {
|
||||
setVisible(hasProgress);
|
||||
}, lifetime());
|
||||
|
||||
paintRequest(
|
||||
@@ -960,6 +957,7 @@ QPoint CancelButton::prepareRippleStartPosition() const {
|
||||
|
||||
void CancelButton::requestPaintProgress(float64 progress) {
|
||||
_showProgress = progress;
|
||||
update();
|
||||
}
|
||||
|
||||
VoiceRecordBar::VoiceRecordBar(
|
||||
|
||||
@@ -868,6 +868,9 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
|
||||
const auto document = linkDocument
|
||||
? linkDocument->document().get()
|
||||
: nullptr;
|
||||
const auto poll = item
|
||||
? (item->media() ? item->media()->poll() : nullptr)
|
||||
: nullptr;
|
||||
const auto hasSelection = !request.selectedItems.empty()
|
||||
|| !request.selectedText.empty();
|
||||
|
||||
@@ -897,6 +900,8 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
|
||||
// });
|
||||
// AddToggleGroupingAction(result, linkPeer->peer());
|
||||
// }
|
||||
} else if (poll) {
|
||||
AddPollActions(result, poll, item, list->elementContext());
|
||||
} else if (!request.overSelection && view && !hasSelection) {
|
||||
const auto owner = &view->data()->history()->owner();
|
||||
const auto media = view->media();
|
||||
@@ -979,4 +984,30 @@ void StopPoll(not_null<Main::Session*> session, FullMsgId itemId) {
|
||||
stop));
|
||||
}
|
||||
|
||||
void AddPollActions(
|
||||
not_null<Ui::PopupMenu*> menu,
|
||||
not_null<PollData*> poll,
|
||||
not_null<HistoryItem*> item,
|
||||
Context context) {
|
||||
if ((context != Context::History)
|
||||
&& (context != Context::Replies)
|
||||
&& (context != Context::Pinned)) {
|
||||
return;
|
||||
}
|
||||
if (poll->closed()) {
|
||||
return;
|
||||
}
|
||||
const auto itemId = item->fullId();
|
||||
if (poll->voted() && !poll->quiz()) {
|
||||
menu->addAction(tr::lng_polls_retract(tr::now), [=] {
|
||||
poll->session().api().sendPollVotes(itemId, {});
|
||||
});
|
||||
}
|
||||
if (item->canStopPoll()) {
|
||||
menu->addAction(tr::lng_polls_stop(tr::now), [=] {
|
||||
StopPoll(&poll->session(), itemId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
|
||||
@@ -49,5 +49,10 @@ void CopyPostLink(
|
||||
FullMsgId itemId,
|
||||
Context context);
|
||||
void StopPoll(not_null<Main::Session*> session, FullMsgId itemId);
|
||||
void AddPollActions(
|
||||
not_null<Ui::PopupMenu*> menu,
|
||||
not_null<PollData*> poll,
|
||||
not_null<HistoryItem*> item,
|
||||
Context context);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "history/view/media/history_view_large_emoji.h"
|
||||
#include "history/history.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -79,7 +80,7 @@ bool SimpleElementDelegate::elementUnderCursor(
|
||||
}
|
||||
|
||||
crl::time SimpleElementDelegate::elementHighlightTime(
|
||||
not_null<const Element*> element) {
|
||||
not_null<const HistoryItem*> item) {
|
||||
return crl::time(0);
|
||||
}
|
||||
|
||||
@@ -157,6 +158,40 @@ TextSelection ShiftItemSelection(
|
||||
return ShiftItemSelection(selection, byText.length());
|
||||
}
|
||||
|
||||
QString DateTooltipText(not_null<Element*> view) {
|
||||
const auto format = QLocale::system().dateTimeFormat(QLocale::LongFormat);
|
||||
auto dateText = view->dateTime().toString(format);
|
||||
if (const auto editedDate = view->displayedEditDate()) {
|
||||
dateText += '\n' + tr::lng_edited_date(
|
||||
tr::now,
|
||||
lt_date,
|
||||
base::unixtime::parse(editedDate).toString(format));
|
||||
}
|
||||
if (const auto forwarded = view->data()->Get<HistoryMessageForwarded>()) {
|
||||
dateText += '\n' + tr::lng_forwarded_date(
|
||||
tr::now,
|
||||
lt_date,
|
||||
base::unixtime::parse(forwarded->originalDate).toString(format));
|
||||
if (const auto media = view->media()) {
|
||||
if (media->hidesForwardedInfo()) {
|
||||
dateText += '\n' + tr::lng_forwarded(
|
||||
tr::now,
|
||||
lt_user,
|
||||
(forwarded->originalSender
|
||||
? forwarded->originalSender->shortName()
|
||||
: forwarded->hiddenSenderInfo->firstName));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (const auto msgsigned = view->data()->Get<HistoryMessageSigned>()) {
|
||||
if (msgsigned->isElided && !msgsigned->isAnonymousRank) {
|
||||
dateText += '\n'
|
||||
+ tr::lng_signed_author(tr::now, lt_user, msgsigned->author);
|
||||
}
|
||||
}
|
||||
return dateText;
|
||||
}
|
||||
|
||||
void UnreadBar::init(const QString &string) {
|
||||
text = string;
|
||||
width = st::semiboldFont->width(text);
|
||||
@@ -280,29 +315,44 @@ void Element::refreshDataIdHook() {
|
||||
void Element::paintHighlight(
|
||||
Painter &p,
|
||||
int geometryHeight) const {
|
||||
const auto animms = delegate()->elementHighlightTime(this);
|
||||
if (!animms
|
||||
|| animms >= st::activeFadeInDuration + st::activeFadeOutDuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto top = marginTop();
|
||||
const auto bottom = marginBottom();
|
||||
const auto fill = qMin(top, bottom);
|
||||
const auto skiptop = top - fill;
|
||||
const auto fillheight = fill + geometryHeight + fill;
|
||||
|
||||
const auto dt = (animms > st::activeFadeInDuration)
|
||||
paintCustomHighlight(p, skiptop, fillheight, data());
|
||||
}
|
||||
|
||||
float64 Element::highlightOpacity(not_null<const HistoryItem*> item) const {
|
||||
const auto animms = delegate()->elementHighlightTime(item);
|
||||
if (!animms
|
||||
|| animms >= st::activeFadeInDuration + st::activeFadeOutDuration) {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
return (animms > st::activeFadeInDuration)
|
||||
? (1. - (animms - st::activeFadeInDuration)
|
||||
/ float64(st::activeFadeOutDuration))
|
||||
: (animms / float64(st::activeFadeInDuration));
|
||||
}
|
||||
|
||||
void Element::paintCustomHighlight(
|
||||
Painter &p,
|
||||
int y,
|
||||
int height,
|
||||
not_null<const HistoryItem*> item) const {
|
||||
const auto opacity = highlightOpacity(item);
|
||||
if (opacity == 0.) {
|
||||
return;
|
||||
}
|
||||
const auto o = p.opacity();
|
||||
p.setOpacity(o * dt);
|
||||
p.setOpacity(o * opacity);
|
||||
p.fillRect(
|
||||
0,
|
||||
skiptop,
|
||||
y,
|
||||
width(),
|
||||
fillheight,
|
||||
height,
|
||||
st::defaultTextPalette.selectOverlay);
|
||||
p.setOpacity(o);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
Element *replacing = nullptr) = 0;
|
||||
virtual bool elementUnderCursor(not_null<const Element*> view) = 0;
|
||||
virtual crl::time elementHighlightTime(
|
||||
not_null<const Element*> element) = 0;
|
||||
not_null<const HistoryItem*> item) = 0;
|
||||
virtual bool elementInSelectionMode() = 0;
|
||||
virtual bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
Element *replacing = nullptr) override;
|
||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||
crl::time elementHighlightTime(
|
||||
not_null<const Element*> element) override;
|
||||
not_null<const HistoryItem*> item) override;
|
||||
bool elementInSelectionMode() override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
@@ -126,6 +126,8 @@ TextSelection ShiftItemSelection(
|
||||
TextSelection selection,
|
||||
const Ui::Text::String &byText);
|
||||
|
||||
QString DateTooltipText(not_null<Element*> view);
|
||||
|
||||
// Any HistoryView::Element can have this Component for
|
||||
// displaying the unread messages bar above the message.
|
||||
struct UnreadBar : public RuntimeComponent<UnreadBar, Element> {
|
||||
@@ -301,6 +303,13 @@ public:
|
||||
virtual void unloadHeavyPart();
|
||||
void checkHeavyPart();
|
||||
|
||||
void paintCustomHighlight(
|
||||
Painter &p,
|
||||
int y,
|
||||
int height,
|
||||
not_null<const HistoryItem*> item) const;
|
||||
float64 highlightOpacity(not_null<const HistoryItem*> item) const;
|
||||
|
||||
// Legacy blocks structure.
|
||||
HistoryBlock *block();
|
||||
const HistoryBlock *block() const;
|
||||
|
||||
@@ -321,6 +321,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||
call->fullCountValue(
|
||||
) | rpl::start_with_next([=](int count) {
|
||||
state->current.count = count;
|
||||
state->current.shown = (count > 0);
|
||||
consumer.put_next_copy(state->current);
|
||||
}, lifetime);
|
||||
|
||||
|
||||
@@ -482,7 +482,7 @@ void ListWidget::highlightMessage(FullMsgId itemId) {
|
||||
_highlightedMessageId = itemId;
|
||||
_highlightTimer.callEach(AnimationTimerDelta);
|
||||
|
||||
repaintItem(view);
|
||||
repaintHighlightedItem(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,10 +496,24 @@ void ListWidget::showAroundPosition(
|
||||
refreshViewer();
|
||||
}
|
||||
|
||||
void ListWidget::repaintHighlightedItem(not_null<const Element*> view) {
|
||||
if (view->isHiddenByGroup()) {
|
||||
if (const auto group = session().data().groups().find(view->data())) {
|
||||
if (const auto leader = viewForItem(group->items.front())) {
|
||||
if (!leader->isHiddenByGroup()) {
|
||||
repaintItem(leader);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
repaintItem(view);
|
||||
}
|
||||
|
||||
void ListWidget::updateHighlightedMessage() {
|
||||
if (const auto item = session().data().message(_highlightedMessageId)) {
|
||||
if (const auto view = viewForItem(item)) {
|
||||
repaintItem(view);
|
||||
repaintHighlightedItem(view);
|
||||
auto duration = st::activeFadeInDuration + st::activeFadeOutDuration;
|
||||
if (crl::now() - _highlightStart <= duration) {
|
||||
return;
|
||||
@@ -1202,8 +1216,7 @@ QString ListWidget::tooltipText() const {
|
||||
? _overElement->data().get()
|
||||
: nullptr;
|
||||
if (_mouseCursorState == CursorState::Date && item) {
|
||||
return _overElement->dateTime().toString(
|
||||
QLocale::system().dateTimeFormat(QLocale::LongFormat));
|
||||
return HistoryView::DateTooltipText(_overElement);
|
||||
} else if (_mouseCursorState == CursorState::Forwarded && item) {
|
||||
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
|
||||
return forwarded->text.toString();
|
||||
@@ -1244,8 +1257,8 @@ bool ListWidget::elementUnderCursor(
|
||||
}
|
||||
|
||||
crl::time ListWidget::elementHighlightTime(
|
||||
not_null<const HistoryView::Element*> element) {
|
||||
if (element->data()->fullId() == _highlightedMessageId) {
|
||||
not_null<const HistoryItem*> item) {
|
||||
if (item->fullId() == _highlightedMessageId) {
|
||||
if (_highlightTimer.isActive()) {
|
||||
return crl::now() - _highlightStart;
|
||||
}
|
||||
@@ -1818,7 +1831,7 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
_overState));
|
||||
|
||||
_menu = FillContextMenu(this, request);
|
||||
if (_menu && !_menu->actions().empty()) {
|
||||
if (_menu && !_menu->empty()) {
|
||||
_menu->popup(e->globalPos());
|
||||
e->accept();
|
||||
} else if (_menu) {
|
||||
|
||||
@@ -221,7 +221,7 @@ public:
|
||||
Element *replacing = nullptr) override;
|
||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||
crl::time elementHighlightTime(
|
||||
not_null<const Element*> element) override;
|
||||
not_null<const HistoryItem*> item) override;
|
||||
bool elementInSelectionMode() override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
@@ -340,6 +340,7 @@ private:
|
||||
int itemTop(not_null<const Element*> view) const;
|
||||
void repaintItem(FullMsgId itemId);
|
||||
void repaintItem(const Element *view);
|
||||
void repaintHighlightedItem(not_null<const Element*> view);
|
||||
void resizeItem(not_null<Element*> view);
|
||||
void refreshItem(not_null<const Element*> view);
|
||||
void itemRemoved(not_null<const HistoryItem*> item);
|
||||
|
||||
@@ -570,7 +570,40 @@ void Message::draw(
|
||||
return;
|
||||
}
|
||||
|
||||
paintHighlight(p, g.height());
|
||||
auto entry = logEntryOriginal();
|
||||
auto mediaDisplayed = media && media->isDisplayed();
|
||||
|
||||
// Entry page is always a bubble bottom.
|
||||
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
|
||||
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
|
||||
|
||||
auto mediaSelectionIntervals = (!selected && mediaDisplayed)
|
||||
? media->getBubbleSelectionIntervals(selection)
|
||||
: std::vector<BubbleSelectionInterval>();
|
||||
auto localMediaTop = 0;
|
||||
const auto customHighlight = mediaDisplayed && media->customHighlight();
|
||||
if (!mediaSelectionIntervals.empty() || customHighlight) {
|
||||
auto localMediaBottom = g.top() + g.height();
|
||||
if (data()->repliesAreComments() || data()->externalReply()) {
|
||||
localMediaBottom -= st::historyCommentsButtonHeight;
|
||||
}
|
||||
if (!mediaOnBottom) {
|
||||
localMediaBottom -= st::msgPadding.bottom();
|
||||
}
|
||||
if (entry) {
|
||||
localMediaBottom -= entry->height();
|
||||
}
|
||||
localMediaTop = localMediaBottom - media->height();
|
||||
for (auto &[top, height] : mediaSelectionIntervals) {
|
||||
top += localMediaTop;
|
||||
}
|
||||
}
|
||||
|
||||
if (customHighlight) {
|
||||
media->drawHighlight(p, localMediaTop);
|
||||
} else {
|
||||
paintHighlight(p, g.height());
|
||||
}
|
||||
|
||||
const auto roll = media ? media->bubbleRoll() : Media::BubbleRoll();
|
||||
if (roll) {
|
||||
@@ -602,34 +635,6 @@ void Message::draw(
|
||||
fromNameUpdated(g.width());
|
||||
}
|
||||
|
||||
auto entry = logEntryOriginal();
|
||||
auto mediaDisplayed = media && media->isDisplayed();
|
||||
|
||||
// Entry page is always a bubble bottom.
|
||||
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
|
||||
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
|
||||
|
||||
|
||||
auto mediaSelectionIntervals = (!selected && mediaDisplayed)
|
||||
? media->getBubbleSelectionIntervals(selection)
|
||||
: std::vector<BubbleSelectionInterval>();
|
||||
if (!mediaSelectionIntervals.empty()) {
|
||||
auto localMediaBottom = g.top() + g.height();
|
||||
if (data()->repliesAreComments() || data()->externalReply()) {
|
||||
localMediaBottom -= st::historyCommentsButtonHeight;
|
||||
}
|
||||
if (!mediaOnBottom) {
|
||||
localMediaBottom -= st::msgPadding.bottom();
|
||||
}
|
||||
if (entry) {
|
||||
localMediaBottom -= entry->height();
|
||||
}
|
||||
const auto localMediaTop = localMediaBottom - media->height();
|
||||
for (auto &[top, height] : mediaSelectionIntervals) {
|
||||
top += localMediaTop;
|
||||
}
|
||||
}
|
||||
|
||||
auto skipTail = isAttachedToNext()
|
||||
|| (media && media->skipBubbleTail())
|
||||
|| (keyboard != nullptr)
|
||||
|
||||
@@ -492,13 +492,11 @@ void RepliesWidget::setupComposeControls() {
|
||||
) | rpl::start_with_next([=](not_null<QKeyEvent*> e) {
|
||||
if (e->key() == Qt::Key_Up) {
|
||||
if (!_composeControls->isEditingMessage()) {
|
||||
// #TODO replies edit last sent message
|
||||
//auto &messages = session().data().scheduledMessages();
|
||||
//if (const auto item = messages.lastSentMessage(_history)) {
|
||||
// _inner->editMessageRequestNotify(item->fullId());
|
||||
//} else {
|
||||
if (const auto item = _replies->lastEditableMessage()) {
|
||||
_inner->editMessageRequestNotify(item->fullId());
|
||||
} else {
|
||||
_scroll->keyPressEvent(e);
|
||||
//}
|
||||
}
|
||||
} else {
|
||||
_scroll->keyPressEvent(e);
|
||||
}
|
||||
|
||||
@@ -235,8 +235,9 @@ void ScheduledWidget::setupComposeControls() {
|
||||
) | rpl::start_with_next([=](not_null<QKeyEvent*> e) {
|
||||
if (e->key() == Qt::Key_Up) {
|
||||
if (!_composeControls->isEditingMessage()) {
|
||||
auto &messages = session().data().scheduledMessages();
|
||||
if (const auto item = messages.lastSentMessage(_history)) {
|
||||
const auto item = session().data().scheduledMessages()
|
||||
.lastEditableMessage(_history);
|
||||
if (item) {
|
||||
_inner->editMessageRequestNotify(item->fullId());
|
||||
} else {
|
||||
_scroll->keyPressEvent(e);
|
||||
|
||||
@@ -256,7 +256,7 @@ void TopBarWidget::showMenu() {
|
||||
_controller,
|
||||
_activeChat,
|
||||
addAction);
|
||||
if (_menu->actions().empty()) {
|
||||
if (_menu->empty()) {
|
||||
_menu.destroy();
|
||||
} else {
|
||||
_menu->moveToRight((parentWidget()->width() - width()) + st::topBarMenuPosition.x(), st::topBarMenuPosition.y());
|
||||
@@ -525,12 +525,36 @@ void TopBarWidget::setActiveChat(
|
||||
_activeChat = activeChat;
|
||||
return;
|
||||
}
|
||||
const auto peerChanged = (_activeChat.key.history()
|
||||
!= activeChat.key.history());
|
||||
|
||||
_activeChat = activeChat;
|
||||
_sendAction = sendAction;
|
||||
_titlePeerText.clear();
|
||||
_back->clearState();
|
||||
update();
|
||||
|
||||
if (peerChanged) {
|
||||
_activeChatLifetime.destroy();
|
||||
if (const auto history = _activeChat.key.history()) {
|
||||
session().changes().peerFlagsValue(
|
||||
history->peer,
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
) | rpl::map([=] {
|
||||
return history->peer->groupCall();
|
||||
}) | rpl::distinct_until_changed(
|
||||
) | rpl::map([](Data::GroupCall *call) {
|
||||
return call ? call->fullCountValue() : rpl::single(-1);
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::map([](int count) {
|
||||
return (count == 0);
|
||||
}) | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateControlsVisibility();
|
||||
updateControlsGeometry();
|
||||
}, _activeChatLifetime);
|
||||
}
|
||||
}
|
||||
updateUnreadBadge();
|
||||
refreshInfoButton();
|
||||
if (_menu) {
|
||||
@@ -722,7 +746,12 @@ void TopBarWidget::updateControlsVisibility() {
|
||||
_call->setVisible(historyMode && callsEnabled);
|
||||
const auto groupCallsEnabled = [&] {
|
||||
if (const auto peer = _activeChat.key.peer()) {
|
||||
return peer->canManageGroupCall();
|
||||
if (peer->canManageGroupCall()) {
|
||||
return true;
|
||||
} else if (const auto call = peer->groupCall()) {
|
||||
return (call->fullCount() == 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
|
||||
@@ -130,6 +130,7 @@ private:
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
ActiveChat _activeChat;
|
||||
QString _customTitleText;
|
||||
rpl::lifetime _activeChatLifetime;
|
||||
|
||||
int _selectedCount = 0;
|
||||
bool _canDelete = false;
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "layout.h" // FullSelection
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
@@ -442,14 +443,14 @@ void Document::draw(
|
||||
}
|
||||
} else {
|
||||
p.setPen(Qt::NoPen);
|
||||
if (selected) {
|
||||
p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected);
|
||||
} else {
|
||||
p.setBrush(outbg ? st::msgFileOutBg : st::msgFileInBg);
|
||||
}
|
||||
|
||||
{
|
||||
const auto coverDrawn = _data->isSongWithCover()
|
||||
&& DrawThumbnailAsSongCover(p, _dataMedia, inner, selected);
|
||||
if (!coverDrawn) {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.setBrush(selected
|
||||
? (outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected)
|
||||
: (outbg ? st::msgFileOutBg : st::msgFileInBg));
|
||||
p.drawEllipse(inner);
|
||||
}
|
||||
|
||||
@@ -581,7 +582,7 @@ void Document::ensureDataMediaCreated() const {
|
||||
return;
|
||||
}
|
||||
_dataMedia = _data->createMediaView();
|
||||
if (Get<HistoryDocumentThumbed>()) {
|
||||
if (Get<HistoryDocumentThumbed>() || _data->isSongWithCover()) {
|
||||
_dataMedia->thumbnailWanted(_realParent->fullId());
|
||||
}
|
||||
history()->owner().registerHeavyViewPart(_parent);
|
||||
@@ -951,6 +952,7 @@ void Document::drawGrouped(
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const {
|
||||
p.translate(geometry.topLeft());
|
||||
@@ -1071,4 +1073,49 @@ Ui::Text::String Document::createCaption() {
|
||||
timestampLinkBase);
|
||||
}
|
||||
|
||||
bool DrawThumbnailAsSongCover(
|
||||
Painter &p,
|
||||
const std::shared_ptr<Data::DocumentMedia> &dataMedia,
|
||||
const QRect &rect,
|
||||
const bool selected) {
|
||||
if (!dataMedia) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QPixmap cover;
|
||||
|
||||
const auto ow = rect.width();
|
||||
const auto oh = rect.height();
|
||||
const auto r = ImageRoundRadius::Ellipse;
|
||||
const auto c = RectPart::AllCorners;
|
||||
const auto color = &st::songCoverOverlayFg;
|
||||
const auto aspectRatio = Qt::KeepAspectRatioByExpanding;
|
||||
|
||||
const auto scaled = [&](not_null<Image*> image) -> std::pair<int, int> {
|
||||
const auto size = image->size().scaled(ow, oh, aspectRatio);
|
||||
return { size.width(), size.height() };
|
||||
};
|
||||
|
||||
if (const auto normal = dataMedia->thumbnail()) {
|
||||
const auto &[w, h] = scaled(normal);
|
||||
cover = normal->pixSingle(w, h, ow, oh, r, c, color);
|
||||
} else if (const auto blurred = dataMedia->thumbnailInline()) {
|
||||
const auto &[w, h] = scaled(blurred);
|
||||
cover = blurred->pixBlurredSingle(w, h, ow, oh, r, c, color);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (selected) {
|
||||
auto selectedCover = Images::prepareColored(
|
||||
p.textPalette().selectOverlay,
|
||||
cover.toImage());
|
||||
cover = QPixmap::fromImage(
|
||||
std::move(selectedCover),
|
||||
Qt::ColorOnly);
|
||||
}
|
||||
p.drawPixmap(rect.topLeft(), cover);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
|
||||
@@ -72,6 +72,7 @@ public:
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const override;
|
||||
TextState getStateGrouped(
|
||||
@@ -142,4 +143,10 @@ private:
|
||||
|
||||
};
|
||||
|
||||
bool DrawThumbnailAsSongCover(
|
||||
Painter &p,
|
||||
const std::shared_ptr<Data::DocumentMedia> &dataMedia,
|
||||
const QRect &rect,
|
||||
const bool selected = false);
|
||||
|
||||
} // namespace HistoryView
|
||||
|
||||
@@ -901,6 +901,7 @@ void Gif::drawGrouped(
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const {
|
||||
ensureDataMediaCreated();
|
||||
@@ -989,8 +990,16 @@ void Gif::drawGrouped(
|
||||
p.drawPixmap(geometry, *cache);
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
const auto overlayOpacity = selected
|
||||
? (1. - highlightOpacity)
|
||||
: highlightOpacity;
|
||||
if (overlayOpacity > 0.) {
|
||||
p.setOpacity(overlayOpacity);
|
||||
Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
|
||||
if (!selected) {
|
||||
Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
if (radial
|
||||
|
||||
@@ -79,6 +79,7 @@ public:
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const override;
|
||||
TextState getStateGrouped(
|
||||
|
||||
@@ -84,6 +84,8 @@ public:
|
||||
}
|
||||
virtual void refreshParentId(not_null<HistoryItem*> realParent) {
|
||||
}
|
||||
virtual void drawHighlight(Painter &p, int top) const {
|
||||
}
|
||||
virtual void draw(
|
||||
Painter &p,
|
||||
const QRect &r,
|
||||
@@ -177,6 +179,7 @@ public:
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const {
|
||||
Unexpected("Grouping method call.");
|
||||
@@ -274,6 +277,9 @@ public:
|
||||
const QRect &bubble,
|
||||
crl::time ms) const {
|
||||
}
|
||||
[[nodiscard]] virtual bool customHighlight() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool hasHeavyPart() const {
|
||||
return false;
|
||||
|
||||
@@ -268,6 +268,18 @@ QMargins GroupedMedia::groupedPadding() const {
|
||||
(normal.bottom() - grouped.bottom()) + addToBottom);
|
||||
}
|
||||
|
||||
void GroupedMedia::drawHighlight(Painter &p, int top) const {
|
||||
if (_mode != Mode::Column) {
|
||||
return;
|
||||
}
|
||||
const auto skip = top + groupedPadding().top();
|
||||
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
|
||||
const auto &part = _parts[i];
|
||||
const auto rect = part.geometry.translated(0, skip);
|
||||
_parent->paintCustomHighlight(p, rect.y(), rect.height(), part.item);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupedMedia::draw(
|
||||
Painter &p,
|
||||
const QRect &clip,
|
||||
@@ -290,6 +302,9 @@ void GroupedMedia::draw(
|
||||
if (textSelection) {
|
||||
selection = part.content->skipSelection(selection);
|
||||
}
|
||||
const auto highlightOpacity = (_mode == Mode::Grid)
|
||||
? _parent->highlightOpacity(part.item)
|
||||
: 0.;
|
||||
part.content->drawGrouped(
|
||||
p,
|
||||
clip,
|
||||
@@ -298,6 +313,7 @@ void GroupedMedia::draw(
|
||||
part.geometry.translated(0, groupPadding.top()),
|
||||
part.sides,
|
||||
cornersFromSides(part.sides),
|
||||
highlightOpacity,
|
||||
&part.cacheKey,
|
||||
&part.cache);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
|
||||
void refreshParentId(not_null<HistoryItem*> realParent) override;
|
||||
|
||||
void drawHighlight(Painter &p, int top) const override;
|
||||
void draw(
|
||||
Painter &p,
|
||||
const QRect &clip,
|
||||
@@ -87,6 +88,9 @@ public:
|
||||
bool allowsFastShare() const override {
|
||||
return true;
|
||||
}
|
||||
bool customHighlight() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void stopAnimation() override;
|
||||
void checkAnimation() override;
|
||||
|
||||
@@ -485,6 +485,7 @@ void Photo::drawGrouped(
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const {
|
||||
ensureDataMediaCreated();
|
||||
@@ -509,9 +510,18 @@ void Photo::drawGrouped(
|
||||
// App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
|
||||
}
|
||||
p.drawPixmap(geometry.topLeft(), *cache);
|
||||
if (selected) {
|
||||
|
||||
const auto overlayOpacity = selected
|
||||
? (1. - highlightOpacity)
|
||||
: highlightOpacity;
|
||||
if (overlayOpacity > 0.) {
|
||||
p.setOpacity(overlayOpacity);
|
||||
const auto roundRadius = ImageRoundRadius::Large;
|
||||
Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
|
||||
if (!selected) {
|
||||
Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
const auto displayState = radial
|
||||
|
||||
@@ -68,6 +68,7 @@ public:
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const override;
|
||||
TextState getStateGrouped(
|
||||
|
||||
@@ -765,7 +765,9 @@ void Poll::draw(Painter &p, const QRect &r, TextSelection selection, crl::time m
|
||||
: nullptr;
|
||||
if (animation) {
|
||||
animation->percent.update(progress, anim::linear);
|
||||
animation->filling.update(progress, anim::linear);
|
||||
animation->filling.update(
|
||||
progress,
|
||||
showVotes() ? anim::easeOutCirc : anim::linear);
|
||||
animation->opacity.update(progress, anim::linear);
|
||||
}
|
||||
const auto height = paintAnswer(
|
||||
|
||||
@@ -247,7 +247,7 @@ rpl::producer<int> AdminsCountValue(not_null<PeerData*> peer) {
|
||||
) | rpl::map([=] {
|
||||
return chat->participants.empty()
|
||||
? 0
|
||||
: int(chat->admins.size() + 1); // + creator
|
||||
: int(chat->admins.size() + (chat->creator ? 1 : 0));
|
||||
});
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
@@ -865,16 +866,21 @@ void File::paint(Painter &p, const QRect &clip, const PaintContext *context) con
|
||||
|
||||
auto inner = style::rtlrect(0, st::inlineRowMargin, st::inlineFileSize, st::inlineFileSize, _width);
|
||||
p.setPen(Qt::NoPen);
|
||||
if (isThumbAnimation()) {
|
||||
auto over = _animation->a_thumbOver.value(1.);
|
||||
p.setBrush(anim::brush(st::msgFileInBg, st::msgFileInBgOver, over));
|
||||
} else {
|
||||
bool over = ClickHandler::showAsActive(_document->loading() ? _cancel : _open);
|
||||
p.setBrush(over ? st::msgFileInBgOver : st::msgFileInBg);
|
||||
}
|
||||
|
||||
{
|
||||
const auto coverDrawn = _document->isSongWithCover()
|
||||
&& HistoryView::DrawThumbnailAsSongCover(p, _documentMedia, inner);
|
||||
if (!coverDrawn) {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
if (isThumbAnimation()) {
|
||||
const auto over = _animation->a_thumbOver.value(1.);
|
||||
p.setBrush(
|
||||
anim::brush(st::msgFileInBg, st::msgFileInBgOver, over));
|
||||
} else {
|
||||
const auto over = ClickHandler::showAsActive(_document->loading()
|
||||
? _cancel
|
||||
: _open);
|
||||
p.setBrush(over ? st::msgFileInBgOver : st::msgFileInBg);
|
||||
}
|
||||
p.drawEllipse(inner);
|
||||
}
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@ void Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
SendMenu::DefaultSilentCallback(send),
|
||||
SendMenu::DefaultScheduleCallback(this, type, send));
|
||||
|
||||
if (!_menu->actions().empty()) {
|
||||
if (!_menu->empty()) {
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,10 +478,17 @@ void Widget::resetAccount() {
|
||||
_resetRequest = 0;
|
||||
|
||||
Ui::hideLayer();
|
||||
moveToStep(
|
||||
new SignupWidget(this, _account, getData()),
|
||||
StackAction::Replace,
|
||||
Animate::Forward);
|
||||
if (getData()->phone.isEmpty()) {
|
||||
moveToStep(
|
||||
new QrWidget(this, _account, getData()),
|
||||
StackAction::Replace,
|
||||
Animate::Back);
|
||||
} else {
|
||||
moveToStep(
|
||||
new SignupWidget(this, _account, getData()),
|
||||
StackAction::Replace,
|
||||
Animate::Forward);
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
_resetRequest = 0;
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "main/main_account.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "core/application.h"
|
||||
#include "core/launcher.h"
|
||||
#include "core/shortcuts.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_domain.h" // Storage::StartResult.
|
||||
@@ -386,8 +386,8 @@ void Account::startMtp(std::unique_ptr<MTP::Config> config) {
|
||||
|
||||
auto fields = base::take(_mtpFields);
|
||||
fields.config = std::move(config);
|
||||
fields.deviceModel = Core::App().launcher()->deviceModel();
|
||||
fields.systemVersion = Core::App().launcher()->systemVersion();
|
||||
fields.deviceModel = Platform::DeviceModelPretty();
|
||||
fields.systemVersion = Platform::SystemVersionPretty();
|
||||
_mtp = std::make_unique<MTP::Instance>(
|
||||
MTP::Instance::Mode::Normal,
|
||||
std::move(fields));
|
||||
@@ -534,8 +534,8 @@ void Account::destroyMtpKeys(MTP::AuthKeysList &&keys) {
|
||||
destroyFields.mainDcId = MTP::Instance::Fields::kNoneMainDc;
|
||||
destroyFields.config = std::make_unique<MTP::Config>(_mtp->config());
|
||||
destroyFields.keys = std::move(keys);
|
||||
destroyFields.deviceModel = Core::App().launcher()->deviceModel();
|
||||
destroyFields.systemVersion = Core::App().launcher()->systemVersion();
|
||||
destroyFields.deviceModel = Platform::DeviceModelPretty();
|
||||
destroyFields.systemVersion = Platform::SystemVersionPretty();
|
||||
_mtpForKeysDestroy = std::make_unique<MTP::Instance>(
|
||||
MTP::Instance::Mode::KeysDestroyer,
|
||||
std::move(destroyFields));
|
||||
|
||||
@@ -146,11 +146,24 @@ void MainWindow::createTrayIconMenu() {
|
||||
: tr::lng_enable_notifications_from_tray(tr::now);
|
||||
|
||||
if (Platform::IsLinux() && !Platform::IsWayland()) {
|
||||
trayIconMenu->addAction(tr::lng_open_from_tray(tr::now), this, SLOT(showFromTray()));
|
||||
trayIconMenu->addAction(tr::lng_open_from_tray(tr::now), [=] {
|
||||
showFromTray();
|
||||
});
|
||||
}
|
||||
trayIconMenu->addAction(tr::lng_minimize_to_tray(tr::now), this, SLOT(minimizeToTray()));
|
||||
trayIconMenu->addAction(notificationActionText, this, SLOT(toggleDisplayNotifyFromTray()));
|
||||
trayIconMenu->addAction(tr::lng_quit_from_tray(tr::now), this, SLOT(quitFromTray()));
|
||||
const auto showLifetime = std::make_shared<rpl::lifetime>();
|
||||
trayIconMenu->addAction(tr::lng_minimize_to_tray(tr::now), [=] {
|
||||
if (_activeForTrayIconAction) {
|
||||
minimizeToTray();
|
||||
} else {
|
||||
showFromTrayMenu();
|
||||
}
|
||||
});
|
||||
trayIconMenu->addAction(notificationActionText, [=] {
|
||||
toggleDisplayNotifyFromTray();
|
||||
});
|
||||
trayIconMenu->addAction(tr::lng_quit_from_tray(tr::now), [=] {
|
||||
quitFromTray();
|
||||
});
|
||||
|
||||
initTrayMenuHook();
|
||||
}
|
||||
@@ -635,18 +648,17 @@ void MainWindow::updateTrayMenu(bool force) {
|
||||
|
||||
auto actions = trayIconMenu->actions();
|
||||
if (Platform::IsLinux() && !Platform::IsWayland()) {
|
||||
auto minimizeAction = actions.at(1);
|
||||
const auto minimizeAction = actions.at(1);
|
||||
minimizeAction->setEnabled(isVisible());
|
||||
} else {
|
||||
updateIsActive();
|
||||
auto active = Platform::IsWayland() ? isVisible() : isActive();
|
||||
auto toggleAction = actions.at(0);
|
||||
disconnect(toggleAction, SIGNAL(triggered(bool)), this, SLOT(minimizeToTray()));
|
||||
disconnect(toggleAction, SIGNAL(triggered(bool)), this, SLOT(showFromTray()));
|
||||
connect(toggleAction, SIGNAL(triggered(bool)), this, active ? SLOT(minimizeToTray()) : SLOT(showFromTray()));
|
||||
toggleAction->setText(active
|
||||
? tr::lng_minimize_to_tray(tr::now)
|
||||
: tr::lng_open_from_tray(tr::now));
|
||||
const auto active = isActiveForTrayMenu();
|
||||
if (_activeForTrayIconAction != active) {
|
||||
_activeForTrayIconAction = active;
|
||||
const auto toggleAction = actions.at(0);
|
||||
toggleAction->setText(_activeForTrayIconAction
|
||||
? tr::lng_minimize_to_tray(tr::now)
|
||||
: tr::lng_open_from_tray(tr::now));
|
||||
}
|
||||
}
|
||||
auto notificationAction = actions.at(Platform::IsLinux() && !Platform::IsWayland() ? 2 : 1);
|
||||
auto notificationActionText = Core::App().settings().desktopNotify()
|
||||
@@ -657,8 +669,10 @@ void MainWindow::updateTrayMenu(bool force) {
|
||||
psTrayMenuUpdated();
|
||||
}
|
||||
|
||||
void MainWindow::onShowAddContact() {
|
||||
if (isHidden()) showFromTray();
|
||||
void MainWindow::showAddContact() {
|
||||
if (isHidden()) {
|
||||
showFromTray();
|
||||
}
|
||||
|
||||
if (const auto controller = sessionController()) {
|
||||
Ui::show(
|
||||
@@ -667,8 +681,10 @@ void MainWindow::onShowAddContact() {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onShowNewGroup() {
|
||||
if (isHidden()) showFromTray();
|
||||
void MainWindow::showNewGroup() {
|
||||
if (isHidden()) {
|
||||
showFromTray();
|
||||
}
|
||||
|
||||
if (const auto controller = sessionController()) {
|
||||
Ui::show(
|
||||
@@ -677,8 +693,10 @@ void MainWindow::onShowNewGroup() {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onShowNewChannel() {
|
||||
if (isHidden()) showFromTray();
|
||||
void MainWindow::showNewChannel() {
|
||||
if (isHidden()) {
|
||||
showFromTray();
|
||||
}
|
||||
|
||||
if (const auto controller = sessionController()) {
|
||||
Ui::show(
|
||||
@@ -720,24 +738,6 @@ void MainWindow::showLogoutConfirmation() {
|
||||
callback));
|
||||
}
|
||||
|
||||
void MainWindow::quitFromTray() {
|
||||
App::quit();
|
||||
}
|
||||
|
||||
void MainWindow::activate() {
|
||||
bool wasHidden = !isVisible();
|
||||
setWindowState(windowState() & ~Qt::WindowMinimized);
|
||||
setVisible(true);
|
||||
psActivateProcess();
|
||||
activateWindow();
|
||||
controller().updateIsActiveFocus();
|
||||
if (wasHidden) {
|
||||
if (_main) {
|
||||
_main->windowShown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::takeThirdSectionFromLayer() {
|
||||
return _layer ? _layer->takeToThirdSection() : false;
|
||||
}
|
||||
@@ -749,23 +749,12 @@ void MainWindow::fixOrder() {
|
||||
if (_testingThemeWarning) _testingThemeWarning->raise();
|
||||
}
|
||||
|
||||
void MainWindow::showFromTray(QSystemTrayIcon::ActivationReason reason) {
|
||||
if (reason != QSystemTrayIcon::Context) {
|
||||
base::call_delayed(1, this, [this] {
|
||||
updateTrayMenu();
|
||||
updateGlobalMenu();
|
||||
});
|
||||
activate();
|
||||
updateUnreadCounter();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::handleTrayIconActication(
|
||||
QSystemTrayIcon::ActivationReason reason) {
|
||||
updateIsActive();
|
||||
if (Platform::IsMac() && isActive()) {
|
||||
if (trayIcon && !trayIcon->contextMenu()) {
|
||||
showFromTray(reason);
|
||||
showFromTray();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -775,10 +764,10 @@ void MainWindow::handleTrayIconActication(
|
||||
psShowTrayMenu();
|
||||
});
|
||||
} else if (!skipTrayClick()) {
|
||||
if (Platform::IsWayland() ? isVisible() : isActive()) {
|
||||
if (isActiveForTrayMenu()) {
|
||||
minimizeToTray();
|
||||
} else {
|
||||
showFromTray(reason);
|
||||
showFromTray();
|
||||
}
|
||||
_lastTrayClickTime = crl::now();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "platform/platform_specific.h"
|
||||
#include "platform/platform_main_window.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "ui/layers/layer_widget.h"
|
||||
@@ -40,8 +39,6 @@ class LayerStackWidget;
|
||||
class MediaPreviewWidget;
|
||||
|
||||
class MainWindow : public Platform::MainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(not_null<Window::Controller*> controller);
|
||||
~MainWindow();
|
||||
@@ -55,11 +52,17 @@ public:
|
||||
void setupIntro(Intro::EnterPoint point);
|
||||
void setupMain();
|
||||
|
||||
void showSettings();
|
||||
void showAddContact();
|
||||
void showNewGroup();
|
||||
void showNewChannel();
|
||||
|
||||
void setInnerFocus();
|
||||
|
||||
MainWidget *sessionContent() const;
|
||||
|
||||
[[nodiscard]] bool doWeMarkAsRead();
|
||||
|
||||
void activate();
|
||||
|
||||
bool takeThirdSectionFromLayer();
|
||||
|
||||
@@ -115,18 +118,6 @@ protected:
|
||||
void updateIsActiveHook() override;
|
||||
void clearWidgetsHook() override;
|
||||
|
||||
public slots:
|
||||
void showSettings();
|
||||
void setInnerFocus();
|
||||
|
||||
void quitFromTray();
|
||||
void showFromTray(QSystemTrayIcon::ActivationReason reason = QSystemTrayIcon::Unknown);
|
||||
void toggleDisplayNotifyFromTray();
|
||||
|
||||
void onShowAddContact();
|
||||
void onShowNewGroup();
|
||||
void onShowNewChannel();
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool skipTrayClick() const;
|
||||
|
||||
@@ -140,12 +131,15 @@ private:
|
||||
|
||||
void themeUpdated(const Window::Theme::BackgroundUpdate &data);
|
||||
|
||||
void toggleDisplayNotifyFromTray();
|
||||
|
||||
QPixmap grabInner();
|
||||
|
||||
QImage icon16, icon32, icon64, iconbig16, iconbig32, iconbig64;
|
||||
|
||||
crl::time _lastTrayClickTime = 0;
|
||||
QPoint _lastMousePosition;
|
||||
bool _activeForTrayIconAction = true;
|
||||
|
||||
object_ptr<Window::PasscodeLockWidget> _passcodeLock = { nullptr };
|
||||
object_ptr<Intro::Widget> _intro = { nullptr };
|
||||
|
||||
@@ -276,7 +276,16 @@ void StopDetachIfNotUsedSafe() {
|
||||
}
|
||||
|
||||
bool SupportsSpeedControl() {
|
||||
return OpenAL::HasEFXExtension();
|
||||
return OpenAL::HasEFXExtension()
|
||||
&& (alGetEnumValue("AL_AUXILIARY_SEND_FILTER") != 0)
|
||||
&& (alGetEnumValue("AL_DIRECT_FILTER") != 0)
|
||||
&& (alGetEnumValue("AL_EFFECT_TYPE") != 0)
|
||||
&& (alGetEnumValue("AL_EFFECT_PITCH_SHIFTER") != 0)
|
||||
&& (alGetEnumValue("AL_FILTER_TYPE") != 0)
|
||||
&& (alGetEnumValue("AL_FILTER_LOWPASS") != 0)
|
||||
&& (alGetEnumValue("AL_LOWPASS_GAIN") != 0)
|
||||
&& (alGetEnumValue("AL_PITCH_SHIFTER_COARSE_TUNE") != 0)
|
||||
&& (alGetEnumValue("AL_EFFECTSLOT_EFFECT") != 0);
|
||||
}
|
||||
|
||||
} // namespace Audio
|
||||
@@ -587,7 +596,7 @@ Mixer::Mixer(not_null<Audio::Instance*> instance)
|
||||
}, _lifetime);
|
||||
|
||||
connect(this, SIGNAL(loaderOnStart(const AudioMsgId&, qint64)), _loader, SLOT(onStart(const AudioMsgId&, qint64)));
|
||||
connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&)));
|
||||
connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&)), Qt::QueuedConnection);
|
||||
connect(_loader, SIGNAL(needToCheck()), _fader, SLOT(onTimer()));
|
||||
connect(_loader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&)));
|
||||
connect(_fader, SIGNAL(needToPreload(const AudioMsgId&)), _loader, SLOT(onLoad(const AudioMsgId&)));
|
||||
|
||||
@@ -148,6 +148,10 @@ Stream File::Context::initStream(
|
||||
|
||||
const auto info = format->streams[index];
|
||||
if (type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (info->disposition & AV_DISPOSITION_ATTACHED_PIC) {
|
||||
// ignore cover streams
|
||||
return Stream();
|
||||
}
|
||||
result.rotation = FFmpeg::ReadRotationFromMetadata(info);
|
||||
result.aspect = FFmpeg::ValidateAspectRatio(info->sample_aspect_ratio);
|
||||
} else if (type == AVMEDIA_TYPE_AUDIO) {
|
||||
@@ -159,10 +163,6 @@ Stream File::Context::initStream(
|
||||
|
||||
result.codec = FFmpeg::MakeCodecPointer(info);
|
||||
if (!result.codec) {
|
||||
if (info->codecpar->codec_id == AV_CODEC_ID_MJPEG) {
|
||||
// mp3 files contain such "video stream", just ignore it.
|
||||
return Stream();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -322,8 +322,7 @@ OverlayWidget::OverlayWidget()
|
||||
, _radial([=](crl::time now) { return radialAnimationCallback(now); })
|
||||
, _lastAction(-st::mediaviewDeltaFromLastAction, -st::mediaviewDeltaFromLastAction)
|
||||
, _stateAnimation([=](crl::time now) { return stateAnimationCallback(now); })
|
||||
, _dropdown(this, st::mediaviewDropdownMenu)
|
||||
, _dropdownShowTimer(this) {
|
||||
, _dropdown(this, st::mediaviewDropdownMenu) {
|
||||
Lang::Updated(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshLang();
|
||||
@@ -360,6 +359,7 @@ OverlayWidget::OverlayWidget()
|
||||
setWindowFlags(Qt::FramelessWindowHint);
|
||||
}
|
||||
updateGeometry();
|
||||
updateControlsGeometry();
|
||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
setMouseTracking(true);
|
||||
@@ -409,23 +409,19 @@ OverlayWidget::OverlayWidget()
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
_saveMsgUpdater.setSingleShot(true);
|
||||
connect(&_saveMsgUpdater, SIGNAL(timeout()), this, SLOT(updateImage()));
|
||||
_saveMsgUpdater.setCallback([=] { updateImage(); });
|
||||
|
||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
_touchTimer.setSingleShot(true);
|
||||
connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
|
||||
_touchTimer.setCallback([=] { onTouchTimer(); });
|
||||
|
||||
_controlsHideTimer.setSingleShot(true);
|
||||
connect(&_controlsHideTimer, SIGNAL(timeout()), this, SLOT(onHideControls()));
|
||||
_controlsHideTimer.setCallback([=] { onHideControls(); });
|
||||
|
||||
_docDownload->addClickHandler([=] { onDownload(); });
|
||||
_docSaveAs->addClickHandler([=] { onSaveAs(); });
|
||||
_docCancel->addClickHandler([=] { onSaveCancel(); });
|
||||
|
||||
_dropdown->setHiddenCallback([this] { dropdownHidden(); });
|
||||
_dropdownShowTimer->setSingleShot(true);
|
||||
connect(_dropdownShowTimer, SIGNAL(timeout()), this, SLOT(onDropdown()));
|
||||
_dropdownShowTimer.setCallback([=] { onDropdown(); });
|
||||
}
|
||||
|
||||
void OverlayWidget::refreshLang() {
|
||||
@@ -446,17 +442,16 @@ void OverlayWidget::moveToScreen() {
|
||||
: nullptr;
|
||||
const auto activeWindowScreen = widgetScreen(window);
|
||||
const auto myScreen = widgetScreen(this);
|
||||
// Wayland doesn't support positioning, but Qt emits screenChanged anyway
|
||||
// and geometry of the widget become broken
|
||||
if (activeWindowScreen
|
||||
&& myScreen != activeWindowScreen
|
||||
&& !Platform::IsWayland()) {
|
||||
if (activeWindowScreen && myScreen != activeWindowScreen) {
|
||||
windowHandle()->setScreen(activeWindowScreen);
|
||||
}
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void OverlayWidget::updateGeometry() {
|
||||
if (Platform::IsLinux()) {
|
||||
return;
|
||||
}
|
||||
const auto screen = windowHandle() && windowHandle()->screen()
|
||||
? windowHandle()->screen()
|
||||
: QApplication::primaryScreen();
|
||||
@@ -465,7 +460,13 @@ void OverlayWidget::updateGeometry() {
|
||||
return;
|
||||
}
|
||||
setGeometry(available);
|
||||
}
|
||||
|
||||
void OverlayWidget::resizeEvent(QResizeEvent *e) {
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
void OverlayWidget::updateControlsGeometry() {
|
||||
auto navSkip = 2 * st::mediaviewControlMargin + st::mediaviewControlSize;
|
||||
_closeNav = myrtlrect(width() - st::mediaviewControlMargin - st::mediaviewControlSize, st::mediaviewControlMargin, st::mediaviewControlSize, st::mediaviewControlSize);
|
||||
_closeNavIcon = style::centerrect(_closeNav, st::mediaviewClose);
|
||||
@@ -477,6 +478,7 @@ void OverlayWidget::updateGeometry() {
|
||||
_saveMsg.moveTo((width() - _saveMsg.width()) / 2, (height() - _saveMsg.height()) / 2);
|
||||
_photoRadialRect = QRect(QPoint((width() - st::radialSize.width()) / 2, (height() - st::radialSize.height()) / 2), st::radialSize);
|
||||
|
||||
updateControls();
|
||||
resizeContentByScreenSize();
|
||||
update();
|
||||
}
|
||||
@@ -791,33 +793,37 @@ void OverlayWidget::refreshCaptionGeometry() {
|
||||
captionHeight);
|
||||
}
|
||||
|
||||
void OverlayWidget::updateActions() {
|
||||
_actions.clear();
|
||||
|
||||
void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) {
|
||||
if (_document && _document->loading()) {
|
||||
_actions.push_back({ tr::lng_cancel(tr::now), SLOT(onSaveCancel()) });
|
||||
addAction(tr::lng_cancel(tr::now), [=] { onSaveCancel(); });
|
||||
}
|
||||
if (IsServerMsgId(_msgid.msg)) {
|
||||
_actions.push_back({ tr::lng_context_to_msg(tr::now), SLOT(onToMessage()) });
|
||||
addAction(tr::lng_context_to_msg(tr::now), [=] { onToMessage(); });
|
||||
}
|
||||
if (_document && !_document->filepath(true).isEmpty()) {
|
||||
_actions.push_back({ Platform::IsMac() ? tr::lng_context_show_in_finder(tr::now) : tr::lng_context_show_in_folder(tr::now), SLOT(onShowInFolder()) });
|
||||
const auto text = Platform::IsMac()
|
||||
? tr::lng_context_show_in_finder(tr::now)
|
||||
: tr::lng_context_show_in_folder(tr::now);
|
||||
addAction(text, [=] { onShowInFolder(); });
|
||||
}
|
||||
if ((_document && documentContentShown()) || (_photo && _photoMedia->loaded())) {
|
||||
_actions.push_back({ tr::lng_mediaview_copy(tr::now), SLOT(onCopy()) });
|
||||
addAction(tr::lng_mediaview_copy(tr::now), [=] { onCopy(); });
|
||||
}
|
||||
if ((_photo && _photo->hasAttachedStickers())
|
||||
|| (_document && _document->hasAttachedStickers())) {
|
||||
auto member = _photo
|
||||
? SLOT(onPhotoAttachedStickers())
|
||||
: SLOT(onDocumentAttachedStickers());
|
||||
_actions.push_back({
|
||||
auto callback = [=] {
|
||||
if (_photo) {
|
||||
onPhotoAttachedStickers();
|
||||
} else if (_document) {
|
||||
onDocumentAttachedStickers();
|
||||
}
|
||||
};
|
||||
addAction(
|
||||
tr::lng_context_attached_stickers(tr::now),
|
||||
std::move(member)
|
||||
});
|
||||
std::move(callback));
|
||||
}
|
||||
if (_canForwardItem) {
|
||||
_actions.push_back({ tr::lng_mediaview_forward(tr::now), SLOT(onForward()) });
|
||||
addAction(tr::lng_mediaview_forward(tr::now), [=] { onForward(); });
|
||||
}
|
||||
const auto canDelete = [&] {
|
||||
if (_canDeleteItem) {
|
||||
@@ -837,12 +843,15 @@ void OverlayWidget::updateActions() {
|
||||
return false;
|
||||
}();
|
||||
if (canDelete) {
|
||||
_actions.push_back({ tr::lng_mediaview_delete(tr::now), SLOT(onDelete()) });
|
||||
addAction(tr::lng_mediaview_delete(tr::now), [=] { onDelete(); });
|
||||
}
|
||||
_actions.push_back({ tr::lng_mediaview_save_as(tr::now), SLOT(onSaveAs()) });
|
||||
addAction(tr::lng_mediaview_save_as(tr::now), [=] { onSaveAs(); });
|
||||
|
||||
if (const auto overviewType = computeOverviewType()) {
|
||||
_actions.push_back({ _document ? tr::lng_mediaview_files_all(tr::now) : tr::lng_mediaview_photos_all(tr::now), SLOT(onOverview()) });
|
||||
const auto text = _document
|
||||
? tr::lng_mediaview_files_all(tr::now)
|
||||
: tr::lng_mediaview_photos_all(tr::now);
|
||||
addAction(text, [=] { onOverview(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1160,8 +1169,6 @@ void OverlayWidget::clearSession() {
|
||||
_animationOpacities.clear();
|
||||
}
|
||||
clearStreaming();
|
||||
delete _menu;
|
||||
_menu = nullptr;
|
||||
setContext(v::null);
|
||||
_from = nullptr;
|
||||
_fromName = QString();
|
||||
@@ -1233,7 +1240,7 @@ void OverlayWidget::close() {
|
||||
|
||||
void OverlayWidget::activateControls() {
|
||||
if (!_menu && !_mousePressed) {
|
||||
_controlsHideTimer.start(int(st::mediaviewWaitHide));
|
||||
_controlsHideTimer.callOnce(st::mediaviewWaitHide);
|
||||
}
|
||||
if (_fullScreenVideo) {
|
||||
if (_streamed) {
|
||||
@@ -1618,7 +1625,9 @@ void OverlayWidget::onDelete() {
|
||||
}
|
||||
|
||||
void OverlayWidget::onOverview() {
|
||||
if (_menu) _menu->hideMenu(true);
|
||||
if (_menu) {
|
||||
_menu->hideMenu(true);
|
||||
}
|
||||
update();
|
||||
if (const auto overviewType = computeOverviewType()) {
|
||||
close();
|
||||
@@ -2443,7 +2452,9 @@ bool OverlayWidget::initStreaming(bool continueStreaming) {
|
||||
void OverlayWidget::startStreamingPlayer() {
|
||||
Expects(_streamed != nullptr);
|
||||
|
||||
if (_streamed->instance.player().playing()) {
|
||||
if (!_streamed->instance.player().paused()
|
||||
&& !_streamed->instance.player().finished()
|
||||
&& !_streamed->instance.player().failed()) {
|
||||
if (!_streamed->withSound) {
|
||||
return;
|
||||
}
|
||||
@@ -3104,7 +3115,7 @@ void OverlayWidget::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
if (!_blurred) {
|
||||
auto nextFrame = (dt < st::mediaviewSaveMsgShowing || hidingDt >= 0) ? int(AnimationTimerDelta) : (st::mediaviewSaveMsgShowing + st::mediaviewSaveMsgShown + 1 - dt);
|
||||
_saveMsgUpdater.start(nextFrame);
|
||||
_saveMsgUpdater.callOnce(nextFrame);
|
||||
}
|
||||
} else {
|
||||
_saveMsgStarted = 0;
|
||||
@@ -3854,7 +3865,9 @@ void OverlayWidget::preloadData(int delta) {
|
||||
|
||||
void OverlayWidget::mousePressEvent(QMouseEvent *e) {
|
||||
updateOver(e->pos());
|
||||
if (_menu || !_receiveMouse) return;
|
||||
if (_menu || !_receiveMouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClickHandler::pressed();
|
||||
|
||||
@@ -3959,9 +3972,9 @@ bool OverlayWidget::updateOverState(OverState newState) {
|
||||
bool result = true;
|
||||
if (_over != newState) {
|
||||
if (newState == OverMore && !_ignoringDropdown) {
|
||||
_dropdownShowTimer->start(0);
|
||||
_dropdownShowTimer.callOnce(0);
|
||||
} else {
|
||||
_dropdownShowTimer->stop();
|
||||
_dropdownShowTimer.cancel();
|
||||
}
|
||||
updateOverRect(_over);
|
||||
updateOverRect(newState);
|
||||
@@ -4095,7 +4108,7 @@ void OverlayWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
} else if (_over == OverIcon && _down == OverIcon) {
|
||||
onDocClick();
|
||||
} else if (_over == OverMore && _down == OverMore) {
|
||||
QTimer::singleShot(0, this, SLOT(onDropdown()));
|
||||
InvokeQueued(this, [=] { onDropdown(); });
|
||||
} else if (_over == OverClose && _down == OverClose) {
|
||||
close();
|
||||
} else if (_over == OverVideo && _down == OverVideo) {
|
||||
@@ -4134,16 +4147,17 @@ void OverlayWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
|
||||
void OverlayWidget::contextMenuEvent(QContextMenuEvent *e) {
|
||||
if (e->reason() != QContextMenuEvent::Mouse || QRect(_x, _y, _w, _h).contains(e->pos())) {
|
||||
if (_menu) {
|
||||
_menu->deleteLater();
|
||||
_menu = nullptr;
|
||||
}
|
||||
_menu = new Ui::PopupMenu(this, st::mediaviewPopupMenu);
|
||||
updateActions();
|
||||
for_const (auto &action, _actions) {
|
||||
_menu->addAction(action.text, this, action.member);
|
||||
}
|
||||
connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*)));
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::mediaviewPopupMenu);
|
||||
fillContextMenuActions([&] (const QString &text, Fn<void()> handler) {
|
||||
_menu->addAction(text, std::move(handler));
|
||||
});
|
||||
_menu->setDestroyedCallback(crl::guard(this, [=] {
|
||||
activateControls();
|
||||
_receiveMouse = false;
|
||||
InvokeQueued(this, [=] { receiveMouse(); });
|
||||
}));
|
||||
_menu->popup(e->globalPos());
|
||||
e->accept();
|
||||
activateControls();
|
||||
@@ -4154,7 +4168,7 @@ void OverlayWidget::touchEvent(QTouchEvent *e) {
|
||||
switch (e->type()) {
|
||||
case QEvent::TouchBegin: {
|
||||
if (_touchPress || e->touchPoints().isEmpty()) return;
|
||||
_touchTimer.start(QApplication::startDragTime());
|
||||
_touchTimer.callOnce(QApplication::startDragTime());
|
||||
_touchPress = true;
|
||||
_touchMove = _touchRightButton = false;
|
||||
_touchStart = e->touchPoints().cbegin()->screenPos().toPoint();
|
||||
@@ -4194,14 +4208,14 @@ void OverlayWidget::touchEvent(QTouchEvent *e) {
|
||||
}
|
||||
}
|
||||
if (weak) {
|
||||
_touchTimer.stop();
|
||||
_touchTimer.cancel();
|
||||
_touchPress = _touchMove = _touchRightButton = false;
|
||||
}
|
||||
} break;
|
||||
|
||||
case QEvent::TouchCancel: {
|
||||
_touchPress = false;
|
||||
_touchTimer.stop();
|
||||
_touchTimer.cancel();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
@@ -4305,8 +4319,10 @@ void OverlayWidget::setVisibleHook(bool visible) {
|
||||
assignMediaPointer(nullptr);
|
||||
_preloadPhotos.clear();
|
||||
_preloadDocuments.clear();
|
||||
if (_menu) _menu->hideMenu(true);
|
||||
_controlsHideTimer.stop();
|
||||
if (_menu) {
|
||||
_menu->hideMenu(true);
|
||||
}
|
||||
_controlsHideTimer.cancel();
|
||||
_controlsState = ControlsShown;
|
||||
_controlsOpacity = anim::value(1, 1);
|
||||
_groupThumbs = nullptr;
|
||||
@@ -4329,25 +4345,15 @@ void OverlayWidget::setVisibleHook(bool visible) {
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayWidget::onMenuDestroy(QObject *obj) {
|
||||
if (_menu == obj) {
|
||||
_menu = nullptr;
|
||||
activateControls();
|
||||
}
|
||||
_receiveMouse = false;
|
||||
QTimer::singleShot(0, this, SLOT(receiveMouse()));
|
||||
}
|
||||
|
||||
void OverlayWidget::receiveMouse() {
|
||||
_receiveMouse = true;
|
||||
}
|
||||
|
||||
void OverlayWidget::onDropdown() {
|
||||
updateActions();
|
||||
_dropdown->clearActions();
|
||||
for_const (auto &action, _actions) {
|
||||
_dropdown->addAction(action.text, this, action.member);
|
||||
}
|
||||
fillContextMenuActions([&] (const QString &text, Fn<void()> handler) {
|
||||
_dropdown->addAction(text, std::move(handler));
|
||||
});
|
||||
_dropdown->moveToRight(0, height() - _dropdown->height());
|
||||
_dropdown->showAnimated(Ui::PanelAnimation::Origin::BottomRight);
|
||||
_dropdown->setFocus();
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
#include "ui/effects/animations.h"
|
||||
@@ -55,10 +56,14 @@ class Pip;
|
||||
#define USE_OPENGL_OVERLAY_WIDGET
|
||||
#endif // Q_OS_MAC && !OS_MAC_OLD
|
||||
|
||||
struct OverlayParentTraits : Ui::RpWidgetDefaultTraits {
|
||||
static constexpr bool kSetZeroGeometry = false;
|
||||
};
|
||||
|
||||
#ifdef USE_OPENGL_OVERLAY_WIDGET
|
||||
using OverlayParent = Ui::RpWidgetWrap<QOpenGLWidget>;
|
||||
using OverlayParent = Ui::RpWidgetWrap<QOpenGLWidget, OverlayParentTraits>;
|
||||
#else // USE_OPENGL_OVERLAY_WIDGET
|
||||
using OverlayParent = Ui::RpWidget;
|
||||
using OverlayParent = Ui::RpWidgetWrap<QWidget, OverlayParentTraits>;
|
||||
#endif // USE_OPENGL_OVERLAY_WIDGET
|
||||
|
||||
class OverlayWidget final
|
||||
@@ -123,7 +128,6 @@ private slots:
|
||||
void onDelete();
|
||||
void onOverview();
|
||||
void onCopy();
|
||||
void onMenuDestroy(QObject *obj);
|
||||
void receiveMouse();
|
||||
void onPhotoAttachedStickers();
|
||||
void onDocumentAttachedStickers();
|
||||
@@ -166,6 +170,7 @@ private:
|
||||
};
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void wheelEvent(QWheelEvent *e) override;
|
||||
@@ -269,7 +274,11 @@ private:
|
||||
void dropdownHidden();
|
||||
void updateDocSize();
|
||||
void updateControls();
|
||||
void updateActions();
|
||||
void updateControlsGeometry();
|
||||
|
||||
using MenuCallback = Fn<void(const QString &, Fn<void()>)>;
|
||||
void fillContextMenuActions(const MenuCallback &addAction);
|
||||
|
||||
void resizeCenteredControls();
|
||||
void resizeContentByScreenSize();
|
||||
|
||||
@@ -487,26 +496,20 @@ private:
|
||||
};
|
||||
ControlsState _controlsState = ControlsShown;
|
||||
crl::time _controlsAnimStarted = 0;
|
||||
QTimer _controlsHideTimer;
|
||||
base::Timer _controlsHideTimer;
|
||||
anim::value _controlsOpacity;
|
||||
bool _mousePressed = false;
|
||||
|
||||
Ui::PopupMenu *_menu = nullptr;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
object_ptr<Ui::DropdownMenu> _dropdown;
|
||||
object_ptr<QTimer> _dropdownShowTimer;
|
||||
|
||||
struct ActionData {
|
||||
QString text;
|
||||
const char *member;
|
||||
};
|
||||
QList<ActionData> _actions;
|
||||
base::Timer _dropdownShowTimer;
|
||||
|
||||
bool _receiveMouse = true;
|
||||
|
||||
bool _touchPress = false;
|
||||
bool _touchMove = false;
|
||||
bool _touchRightButton = false;
|
||||
QTimer _touchTimer;
|
||||
base::Timer _touchTimer;
|
||||
QPoint _touchStart;
|
||||
QPoint _accumScroll;
|
||||
|
||||
@@ -514,7 +517,7 @@ private:
|
||||
crl::time _saveMsgStarted = 0;
|
||||
anim::value _saveMsgOpacity;
|
||||
QRect _saveMsg;
|
||||
QTimer _saveMsgUpdater;
|
||||
base::Timer _saveMsgUpdater;
|
||||
Ui::Text::String _saveMsgText;
|
||||
SavePhotoVideo _savePhotoVideoWhenLoaded = SavePhotoVideo::None;
|
||||
|
||||
|
||||
@@ -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/86.0.4240.198 Safari/537.36");
|
||||
"Chrome/87.0.4280.88 Safari/537.36");
|
||||
return kResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
|
||||
#include "base/unixtime.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "ui/effects/round_checkbox.h"
|
||||
@@ -1008,21 +1009,33 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con
|
||||
|
||||
auto inner = style::rtlrect(_st.songPadding.left(), _st.songPadding.top(), _st.songThumbSize, _st.songThumbSize, _width);
|
||||
if (clip.intersects(inner)) {
|
||||
const auto isLoading = (!cornerDownload
|
||||
&& (_data->loading() || _data->uploading()));
|
||||
p.setPen(Qt::NoPen);
|
||||
if (selected) {
|
||||
p.setBrush(st::msgFileInBgSelected);
|
||||
} else {
|
||||
auto over = ClickHandler::showAsActive((!cornerDownload && (_data->loading() || _data->uploading())) ? _cancell : (loaded || _dataMedia->canBePlayed()) ? _openl : _savel);
|
||||
p.setBrush(anim::brush(_st.songIconBg, _st.songOverBg, _a_iconOver.value(over ? 1. : 0.)));
|
||||
}
|
||||
|
||||
{
|
||||
using namespace HistoryView;
|
||||
const auto coverDrawn = _data->isSongWithCover()
|
||||
&& DrawThumbnailAsSongCover(p, _dataMedia, inner, selected);
|
||||
if (!coverDrawn) {
|
||||
if (selected) {
|
||||
p.setBrush(st::msgFileInBgSelected);
|
||||
} else {
|
||||
const auto over = ClickHandler::showAsActive(isLoading
|
||||
? _cancell
|
||||
: (loaded || _dataMedia->canBePlayed())
|
||||
? _openl
|
||||
: _savel);
|
||||
p.setBrush(anim::brush(
|
||||
_st.songIconBg,
|
||||
_st.songOverBg,
|
||||
_a_iconOver.value(over ? 1. : 0.)));
|
||||
}
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.drawEllipse(inner);
|
||||
}
|
||||
|
||||
const auto icon = [&] {
|
||||
if (!cornerDownload && (_data->loading() || _data->uploading())) {
|
||||
if (isLoading) {
|
||||
return &(selected ? _st.songCancelSelected : _st.songCancel);
|
||||
} else if (showPause) {
|
||||
return &(selected ? _st.songPauseSelected : _st.songPause);
|
||||
@@ -1479,9 +1492,8 @@ Link::Link(
|
||||
int32 tw = 0, th = 0;
|
||||
if (_page && _page->photo) {
|
||||
const auto photo = _page->photo;
|
||||
if (photo->inlineThumbnailBytes().isEmpty()
|
||||
&& (photo->hasExact(Data::PhotoSize::Small)
|
||||
|| photo->hasExact(Data::PhotoSize::Thumbnail))) {
|
||||
if (photo->hasExact(Data::PhotoSize::Small)
|
||||
|| photo->hasExact(Data::PhotoSize::Thumbnail)) {
|
||||
photo->load(Data::PhotoSize::Small, parent->fullId());
|
||||
}
|
||||
tw = style::ConvertScale(photo->width());
|
||||
@@ -1623,7 +1635,7 @@ void Link::paint(Painter &p, const QRect &clip, TextSelection selection, const P
|
||||
}
|
||||
|
||||
void Link::validateThumbnail() {
|
||||
if (!_thumbnail.isNull()) {
|
||||
if (!_thumbnail.isNull() && !_thumbnailBlurred) {
|
||||
return;
|
||||
}
|
||||
if (_page && _page->photo) {
|
||||
@@ -1631,12 +1643,16 @@ void Link::validateThumbnail() {
|
||||
ensurePhotoMediaCreated();
|
||||
if (const auto thumbnail = _photoMedia->image(PhotoSize::Thumbnail)) {
|
||||
_thumbnail = thumbnail->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
|
||||
_thumbnailBlurred = false;
|
||||
} else if (const auto large = _photoMedia->image(PhotoSize::Large)) {
|
||||
_thumbnail = large->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
|
||||
_thumbnailBlurred = false;
|
||||
} else if (const auto small = _photoMedia->image(PhotoSize::Small)) {
|
||||
_thumbnail = small->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
|
||||
_thumbnailBlurred = false;
|
||||
} else if (const auto blurred = _photoMedia->thumbnailInline()) {
|
||||
_thumbnail = blurred->pixBlurredSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -1644,14 +1660,20 @@ void Link::validateThumbnail() {
|
||||
delegate()->unregisterHeavyItem(this);
|
||||
} else if (_page && _page->document && _page->document->hasThumbnail()) {
|
||||
ensureDocumentMediaCreated();
|
||||
const auto roundRadius = _page->document->isVideoMessage()
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::Small;
|
||||
if (const auto thumbnail = _documentMedia->thumbnail()) {
|
||||
auto roundRadius = _page->document->isVideoMessage()
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::Small;
|
||||
_thumbnail = thumbnail->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, roundRadius);
|
||||
_documentMedia = nullptr;
|
||||
delegate()->unregisterHeavyItem(this);
|
||||
_thumbnailBlurred = false;
|
||||
} else if (const auto blurred = _documentMedia->thumbnailInline()) {
|
||||
_thumbnail = blurred->pixBlurredSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, roundRadius);
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
_documentMedia = nullptr;
|
||||
delegate()->unregisterHeavyItem(this);
|
||||
} else {
|
||||
const auto size = QSize(st::linksPhotoSize, st::linksPhotoSize);
|
||||
_thumbnail = QPixmap(size * cIntRetinaFactor());
|
||||
@@ -1683,6 +1705,7 @@ void Link::validateThumbnail() {
|
||||
_letter,
|
||||
style::al_center);
|
||||
}
|
||||
_thumbnailBlurred = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -368,6 +368,7 @@ private:
|
||||
int _pixh = 0;
|
||||
Ui::Text::String _text = { st::msgMinWidth };
|
||||
QPixmap _thumbnail;
|
||||
bool _thumbnailBlurred = true;
|
||||
|
||||
struct LinkEntry {
|
||||
LinkEntry() : width(0) {
|
||||
|
||||
@@ -7,14 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/file_utilities_linux.h"
|
||||
|
||||
#include "platform/linux/linux_libs.h"
|
||||
#include "platform/linux/linux_gdk_helper.h"
|
||||
#include "platform/linux/linux_desktop_environment.h"
|
||||
#include "platform/linux/linux_gtk_integration.h"
|
||||
#include "platform/linux/specific_linux.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "core/application.h"
|
||||
|
||||
#include <QtGui/QDesktopServices>
|
||||
|
||||
@@ -24,100 +18,10 @@ extern "C" {
|
||||
#define signals public
|
||||
} // extern "C"
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
#include <private/qguiapplication_p.h>
|
||||
|
||||
extern "C" {
|
||||
#undef signals
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
#define signals public
|
||||
} // extern "C"
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
using Platform::internal::GtkIntegration;
|
||||
|
||||
namespace Platform {
|
||||
namespace File {
|
||||
namespace {
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
bool ShowOpenWithSupported() {
|
||||
return Platform::internal::GdkHelperLoaded()
|
||||
&& (Libs::gtk_app_chooser_dialog_new != nullptr)
|
||||
&& (Libs::gtk_app_chooser_get_app_info != nullptr)
|
||||
&& (Libs::gtk_app_chooser_get_type != nullptr)
|
||||
&& (Libs::gtk_widget_get_type != nullptr)
|
||||
&& (Libs::gtk_widget_get_window != nullptr)
|
||||
&& (Libs::gtk_widget_realize != nullptr)
|
||||
&& (Libs::gtk_widget_show != nullptr)
|
||||
&& (Libs::gtk_widget_destroy != nullptr);
|
||||
}
|
||||
|
||||
void HandleAppChooserResponse(
|
||||
GtkDialog *dialog,
|
||||
int responseId,
|
||||
GFile *file) {
|
||||
GAppInfo *chosenAppInfo = nullptr;
|
||||
|
||||
switch (responseId) {
|
||||
case GTK_RESPONSE_OK:
|
||||
chosenAppInfo = Libs::gtk_app_chooser_get_app_info(
|
||||
Libs::gtk_app_chooser_cast(dialog));
|
||||
|
||||
if (chosenAppInfo) {
|
||||
GList *uris = nullptr;
|
||||
uris = g_list_prepend(uris, g_file_get_uri(file));
|
||||
g_app_info_launch_uris(chosenAppInfo, uris, nullptr, nullptr);
|
||||
g_list_free(uris);
|
||||
g_object_unref(chosenAppInfo);
|
||||
}
|
||||
|
||||
g_object_unref(file);
|
||||
Libs::gtk_widget_destroy(Libs::gtk_widget_cast(dialog));
|
||||
break;
|
||||
|
||||
case GTK_RESPONSE_CANCEL:
|
||||
case GTK_RESPONSE_DELETE_EVENT:
|
||||
g_object_unref(file);
|
||||
Libs::gtk_widget_destroy(Libs::gtk_widget_cast(dialog));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace internal {
|
||||
|
||||
QByteArray EscapeShell(const QByteArray &content) {
|
||||
auto result = QByteArray();
|
||||
|
||||
auto b = content.constData(), e = content.constEnd();
|
||||
for (auto ch = b; ch != e; ++ch) {
|
||||
if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') {
|
||||
if (result.isEmpty()) {
|
||||
result.reserve(content.size() * 2);
|
||||
}
|
||||
if (ch > b) {
|
||||
result.append(b, ch - b);
|
||||
}
|
||||
result.append('\\');
|
||||
b = ch;
|
||||
}
|
||||
}
|
||||
if (result.isEmpty()) {
|
||||
return content;
|
||||
}
|
||||
|
||||
if (e > b) {
|
||||
result.append(b, e - b);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
void UnsafeOpenUrl(const QString &url) {
|
||||
if (!g_app_info_launch_default_for_uri(
|
||||
@@ -133,41 +37,16 @@ void UnsafeOpenEmailLink(const QString &email) {
|
||||
}
|
||||
|
||||
bool UnsafeShowOpenWith(const QString &filepath) {
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
if (InFlatpak()
|
||||
|| InSnap()
|
||||
|| !ShowOpenWithSupported()) {
|
||||
if (InFlatpak() || InSnap()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto absolutePath = QFileInfo(filepath).absoluteFilePath();
|
||||
auto gfileInstance = g_file_new_for_path(absolutePath.toUtf8());
|
||||
|
||||
auto appChooserDialog = Libs::gtk_app_chooser_dialog_new(
|
||||
nullptr,
|
||||
GTK_DIALOG_MODAL,
|
||||
gfileInstance);
|
||||
|
||||
g_signal_connect(
|
||||
appChooserDialog,
|
||||
"response",
|
||||
G_CALLBACK(HandleAppChooserResponse),
|
||||
gfileInstance);
|
||||
|
||||
Libs::gtk_widget_realize(appChooserDialog);
|
||||
|
||||
if (const auto activeWindow = Core::App().activeWindow()) {
|
||||
Platform::internal::XSetTransientForHint(
|
||||
Libs::gtk_widget_get_window(appChooserDialog),
|
||||
activeWindow->widget().get()->windowHandle()->winId());
|
||||
if (const auto integration = GtkIntegration::Instance()) {
|
||||
const auto absolutePath = QFileInfo(filepath).absoluteFilePath();
|
||||
return integration->showOpenWithDialog(absolutePath);
|
||||
}
|
||||
|
||||
Libs::gtk_widget_show(appChooserDialog);
|
||||
|
||||
return true;
|
||||
#else // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
return false;
|
||||
#endif // TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
}
|
||||
|
||||
void UnsafeLaunch(const QString &filepath) {
|
||||
@@ -186,136 +65,6 @@ void UnsafeLaunch(const QString &filepath) {
|
||||
} // namespace File
|
||||
|
||||
namespace FileDialog {
|
||||
namespace {
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
// GTK file chooser image preview: thanks to Chromium
|
||||
|
||||
// The size of the preview we display for selected image files. We set height
|
||||
// larger than width because generally there is more free space vertically
|
||||
// than horiztonally (setting the preview image will alway expand the width of
|
||||
// the dialog, but usually not the height). The image's aspect ratio will always
|
||||
// be preserved.
|
||||
constexpr auto kPreviewWidth = 256;
|
||||
constexpr auto kPreviewHeight = 512;
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
using Type = ::FileDialog::internal::Type;
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
bool UseNative(Type type = Type::ReadFile) {
|
||||
// use gtk file dialog on gtk-based desktop environments
|
||||
// or if QT_QPA_PLATFORMTHEME=(gtk2|gtk3)
|
||||
// or if portals are used and operation is to open folder
|
||||
// and portal doesn't support folder choosing
|
||||
const auto sandboxedOrCustomPortal = InFlatpak()
|
||||
|| InSnap()
|
||||
|| UseXDGDesktopPortal();
|
||||
|
||||
const auto neededForPortal = (type == Type::ReadFolder)
|
||||
&& !CanOpenDirectoryWithPortal();
|
||||
|
||||
const auto neededNonForced = DesktopEnvironment::IsGtkBased()
|
||||
|| (sandboxedOrCustomPortal && neededForPortal);
|
||||
|
||||
const auto excludeNonForced = sandboxedOrCustomPortal && !neededForPortal;
|
||||
|
||||
return IsGtkIntegrationForced()
|
||||
|| (neededNonForced && !excludeNonForced);
|
||||
}
|
||||
|
||||
bool NativeSupported() {
|
||||
return Platform::internal::GdkHelperLoaded()
|
||||
&& (Libs::gtk_widget_hide_on_delete != nullptr)
|
||||
&& (Libs::gtk_clipboard_store != nullptr)
|
||||
&& (Libs::gtk_clipboard_get != nullptr)
|
||||
&& (Libs::gtk_widget_destroy != nullptr)
|
||||
&& (Libs::gtk_dialog_get_type != nullptr)
|
||||
&& (Libs::gtk_dialog_run != nullptr)
|
||||
&& (Libs::gtk_widget_realize != nullptr)
|
||||
&& (Libs::gdk_window_set_modal_hint != nullptr)
|
||||
&& (Libs::gtk_widget_show != nullptr)
|
||||
&& (Libs::gdk_window_focus != nullptr)
|
||||
&& (Libs::gtk_widget_hide != nullptr)
|
||||
&& (Libs::gtk_widget_hide_on_delete != nullptr)
|
||||
&& (Libs::gtk_file_chooser_dialog_new != nullptr)
|
||||
&& (Libs::gtk_file_chooser_get_type != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_current_folder != nullptr)
|
||||
&& (Libs::gtk_file_chooser_get_current_folder != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_current_name != nullptr)
|
||||
&& (Libs::gtk_file_chooser_select_filename != nullptr)
|
||||
&& (Libs::gtk_file_chooser_get_filenames != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_filter != nullptr)
|
||||
&& (Libs::gtk_file_chooser_get_filter != nullptr)
|
||||
&& (Libs::gtk_window_get_type != nullptr)
|
||||
&& (Libs::gtk_window_set_title != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_local_only != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_action != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_select_multiple != nullptr)
|
||||
&& (Libs::gtk_file_chooser_set_do_overwrite_confirmation != nullptr)
|
||||
&& (Libs::gtk_file_chooser_remove_filter != nullptr)
|
||||
&& (Libs::gtk_file_filter_set_name != nullptr)
|
||||
&& (Libs::gtk_file_filter_add_pattern != nullptr)
|
||||
&& (Libs::gtk_file_chooser_add_filter != nullptr)
|
||||
&& (Libs::gtk_file_filter_new != nullptr);
|
||||
}
|
||||
|
||||
bool PreviewSupported() {
|
||||
return NativeSupported()
|
||||
&& (Libs::gdk_pixbuf_new_from_file_at_size != nullptr);
|
||||
}
|
||||
|
||||
bool GetNative(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
Type type,
|
||||
QString startFile) {
|
||||
internal::GtkFileDialog dialog(parent, caption, QString(), filter);
|
||||
|
||||
dialog.setModal(true);
|
||||
if (type == Type::ReadFile || type == Type::ReadFiles) {
|
||||
dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
} else if (type == Type::ReadFolder) {
|
||||
dialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
dialog.setFileMode(QFileDialog::Directory);
|
||||
dialog.setOption(QFileDialog::ShowDirsOnly);
|
||||
} else {
|
||||
dialog.setFileMode(QFileDialog::AnyFile);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
}
|
||||
if (startFile.isEmpty() || startFile.at(0) != '/') {
|
||||
startFile = cDialogLastPath() + '/' + startFile;
|
||||
}
|
||||
dialog.selectFile(startFile);
|
||||
|
||||
int res = dialog.exec();
|
||||
|
||||
QString path = dialog.directory().absolutePath();
|
||||
if (path != cDialogLastPath()) {
|
||||
cSetDialogLastPath(path);
|
||||
Local::writeSettings();
|
||||
}
|
||||
|
||||
if (res == QDialog::Accepted) {
|
||||
if (type == Type::ReadFiles) {
|
||||
files = dialog.selectedFiles();
|
||||
} else {
|
||||
files = dialog.selectedFiles().mid(0, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
files = QStringList();
|
||||
remoteContent = QByteArray();
|
||||
return false;
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Get(
|
||||
QPointer<QWidget> parent,
|
||||
@@ -323,23 +72,24 @@ bool Get(
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
Type type,
|
||||
::FileDialog::internal::Type type,
|
||||
QString startFile) {
|
||||
if (parent) {
|
||||
parent = parent->window();
|
||||
}
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
if (UseNative(type) && NativeSupported()) {
|
||||
return GetNative(
|
||||
parent,
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
type,
|
||||
startFile);
|
||||
if (const auto integration = GtkIntegration::Instance()) {
|
||||
if (integration->fileDialogSupported()
|
||||
&& integration->useFileDialog(type)) {
|
||||
return integration->getFileDialog(
|
||||
parent,
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
type,
|
||||
startFile);
|
||||
}
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
return ::FileDialog::internal::GetDefault(
|
||||
parent,
|
||||
files,
|
||||
@@ -350,448 +100,5 @@ bool Get(
|
||||
startFile);
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
namespace internal {
|
||||
|
||||
QGtkDialog::QGtkDialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget) {
|
||||
g_signal_connect_swapped(G_OBJECT(gtkWidget), "response", G_CALLBACK(onResponse), this);
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "delete-event", G_CALLBACK(Libs::gtk_widget_hide_on_delete), nullptr);
|
||||
if (PreviewSupported()) {
|
||||
_preview = Libs::gtk_image_new();
|
||||
g_signal_connect_swapped(G_OBJECT(gtkWidget), "update-preview", G_CALLBACK(onUpdatePreview), this);
|
||||
Libs::gtk_file_chooser_set_preview_widget(Libs::gtk_file_chooser_cast(gtkWidget), _preview);
|
||||
}
|
||||
}
|
||||
|
||||
QGtkDialog::~QGtkDialog() {
|
||||
Libs::gtk_clipboard_store(Libs::gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
|
||||
Libs::gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
GtkDialog *QGtkDialog::gtkDialog() const {
|
||||
return Libs::gtk_dialog_cast(gtkWidget);
|
||||
}
|
||||
|
||||
void QGtkDialog::exec() {
|
||||
if (modality() == Qt::ApplicationModal) {
|
||||
// block input to the whole app, including other GTK dialogs
|
||||
Libs::gtk_dialog_run(gtkDialog());
|
||||
} else {
|
||||
// block input to the window, allow input to other GTK dialogs
|
||||
QEventLoop loop;
|
||||
connect(this, SIGNAL(accept()), &loop, SLOT(quit()));
|
||||
connect(this, SIGNAL(reject()), &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void QGtkDialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) {
|
||||
connect(parent, &QWindow::destroyed, this, &QGtkDialog::onParentWindowDestroyed,
|
||||
Qt::UniqueConnection);
|
||||
setParent(parent);
|
||||
setFlags(flags);
|
||||
setModality(modality);
|
||||
|
||||
Libs::gtk_widget_realize(gtkWidget); // creates X window
|
||||
|
||||
if (parent) {
|
||||
Platform::internal::XSetTransientForHint(Libs::gtk_widget_get_window(gtkWidget), parent->winId());
|
||||
}
|
||||
|
||||
if (modality != Qt::NonModal) {
|
||||
Libs::gdk_window_set_modal_hint(Libs::gtk_widget_get_window(gtkWidget), true);
|
||||
QGuiApplicationPrivate::showModalWindow(this);
|
||||
}
|
||||
|
||||
Libs::gtk_widget_show(gtkWidget);
|
||||
Libs::gdk_window_focus(Libs::gtk_widget_get_window(gtkWidget), 0);
|
||||
}
|
||||
|
||||
void QGtkDialog::hide() {
|
||||
QGuiApplicationPrivate::hideModalWindow(this);
|
||||
Libs::gtk_widget_hide(gtkWidget);
|
||||
}
|
||||
|
||||
void QGtkDialog::onResponse(QGtkDialog *dialog, int response) {
|
||||
if (response == GTK_RESPONSE_OK)
|
||||
emit dialog->accept();
|
||||
else
|
||||
emit dialog->reject();
|
||||
}
|
||||
|
||||
void QGtkDialog::onUpdatePreview(QGtkDialog* dialog) {
|
||||
auto filename = Libs::gtk_file_chooser_get_preview_filename(Libs::gtk_file_chooser_cast(dialog->gtkWidget));
|
||||
if (!filename) {
|
||||
Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't attempt to open anything which isn't a regular file. If a named pipe,
|
||||
// this may hang. See https://crbug.com/534754.
|
||||
struct stat stat_buf;
|
||||
if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) {
|
||||
g_free(filename);
|
||||
Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), false);
|
||||
return;
|
||||
}
|
||||
|
||||
// This will preserve the image's aspect ratio.
|
||||
auto pixbuf = Libs::gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth, kPreviewHeight, nullptr);
|
||||
g_free(filename);
|
||||
if (pixbuf) {
|
||||
Libs::gtk_image_set_from_pixbuf(Libs::gtk_image_cast(dialog->_preview), pixbuf);
|
||||
g_object_unref(pixbuf);
|
||||
}
|
||||
Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), pixbuf ? true : false);
|
||||
}
|
||||
|
||||
void QGtkDialog::onParentWindowDestroyed() {
|
||||
// The Gtk*DialogHelper classes own this object. Make sure the parent doesn't delete it.
|
||||
setParent(nullptr);
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
namespace {
|
||||
|
||||
const char *filterRegExp =
|
||||
"^(.*)\\(([a-zA-Z0-9_.,*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$";
|
||||
|
||||
QStringList makeFilterList(const QString &filter) {
|
||||
QString f(filter);
|
||||
|
||||
if (f.isEmpty())
|
||||
return QStringList();
|
||||
|
||||
QString sep(QLatin1String(";;"));
|
||||
int i = f.indexOf(sep, 0);
|
||||
if (i == -1) {
|
||||
if (f.indexOf(QLatin1Char('\n'), 0) != -1) {
|
||||
sep = QLatin1Char('\n');
|
||||
i = f.indexOf(sep, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return f.split(sep);
|
||||
}
|
||||
|
||||
// Makes a list of filters from a normal filter string "Image Files (*.png *.jpg)"
|
||||
QStringList cleanFilterList(const QString &filter) {
|
||||
QRegExp regexp(QString::fromLatin1(filterRegExp));
|
||||
Q_ASSERT(regexp.isValid());
|
||||
QString f = filter;
|
||||
int i = regexp.indexIn(f);
|
||||
if (i >= 0)
|
||||
f = regexp.cap(2);
|
||||
return f.split(QLatin1Char(' '), base::QStringSkipEmptyParts);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
GtkFileDialog::GtkFileDialog(QWidget *parent, const QString &caption, const QString &directory, const QString &filter) : QDialog(parent)
|
||||
, _windowTitle(caption)
|
||||
, _initialDirectory(directory) {
|
||||
auto filters = makeFilterList(filter);
|
||||
const int numFilters = filters.count();
|
||||
_nameFilters.reserve(numFilters);
|
||||
for (int i = 0; i < numFilters; ++i) {
|
||||
_nameFilters << filters[i].simplified();
|
||||
}
|
||||
|
||||
d.reset(new QGtkDialog(Libs::gtk_file_chooser_dialog_new("", nullptr,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
// https://developer.gnome.org/gtk3/stable/GtkFileChooserDialog.html#gtk-file-chooser-dialog-new
|
||||
// first_button_text doesn't need explicit conversion to char*, while all others are vardict
|
||||
tr::lng_cancel(tr::now).toUtf8(), GTK_RESPONSE_CANCEL,
|
||||
tr::lng_box_ok(tr::now).toUtf8().constData(), GTK_RESPONSE_OK, nullptr)));
|
||||
connect(d.data(), SIGNAL(accept()), this, SLOT(onAccepted()));
|
||||
connect(d.data(), SIGNAL(reject()), this, SLOT(onRejected()));
|
||||
|
||||
g_signal_connect(Libs::gtk_file_chooser_cast(d->gtkDialog()), "selection-changed", G_CALLBACK(onSelectionChanged), this);
|
||||
g_signal_connect_swapped(Libs::gtk_file_chooser_cast(d->gtkDialog()), "current-folder-changed", G_CALLBACK(onCurrentFolderChanged), this);
|
||||
}
|
||||
|
||||
GtkFileDialog::~GtkFileDialog() {
|
||||
}
|
||||
|
||||
void GtkFileDialog::showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) {
|
||||
_dir.clear();
|
||||
_selection.clear();
|
||||
|
||||
applyOptions();
|
||||
return d->show(flags, modality, parent);
|
||||
}
|
||||
|
||||
void GtkFileDialog::setVisible(bool visible) {
|
||||
if (visible) {
|
||||
if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) {
|
||||
return;
|
||||
}
|
||||
} else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
showHelper(windowFlags(), windowModality(), parentWidget() ? parentWidget()->windowHandle() : nullptr);
|
||||
} else {
|
||||
hideHelper();
|
||||
}
|
||||
|
||||
// Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below
|
||||
// updates the state correctly, but skips showing the non-native version:
|
||||
setAttribute(Qt::WA_DontShowOnScreen);
|
||||
|
||||
QDialog::setVisible(visible);
|
||||
}
|
||||
|
||||
int GtkFileDialog::exec() {
|
||||
d->setModality(windowModality());
|
||||
|
||||
bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose);
|
||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
|
||||
bool wasShowModal = testAttribute(Qt::WA_ShowModal);
|
||||
setAttribute(Qt::WA_ShowModal, true);
|
||||
setResult(0);
|
||||
|
||||
show();
|
||||
|
||||
QPointer<QDialog> guard = this;
|
||||
d->exec();
|
||||
if (guard.isNull())
|
||||
return QDialog::Rejected;
|
||||
|
||||
setAttribute(Qt::WA_ShowModal, wasShowModal);
|
||||
|
||||
return result();
|
||||
}
|
||||
|
||||
void GtkFileDialog::hideHelper() {
|
||||
// After GtkFileChooserDialog has been hidden, gtk_file_chooser_get_current_folder()
|
||||
// & gtk_file_chooser_get_filenames() will return bogus values -> cache the actual
|
||||
// values before hiding the dialog
|
||||
_dir = directory().absolutePath();
|
||||
_selection = selectedFiles();
|
||||
|
||||
d->hide();
|
||||
}
|
||||
|
||||
bool GtkFileDialog::defaultNameFilterDisables() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void GtkFileDialog::setDirectory(const QString &directory) {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), directory.toUtf8());
|
||||
}
|
||||
|
||||
QDir GtkFileDialog::directory() const {
|
||||
// While GtkFileChooserDialog is hidden, gtk_file_chooser_get_current_folder()
|
||||
// returns a bogus value -> return the cached value before hiding
|
||||
if (!_dir.isEmpty())
|
||||
return _dir;
|
||||
|
||||
QString ret;
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
gchar *folder = Libs::gtk_file_chooser_get_current_folder(Libs::gtk_file_chooser_cast(gtkDialog));
|
||||
if (folder) {
|
||||
ret = QString::fromUtf8(folder);
|
||||
g_free(folder);
|
||||
}
|
||||
return QDir(ret);
|
||||
}
|
||||
|
||||
void GtkFileDialog::selectFile(const QString &filename) {
|
||||
_initialFiles.clear();
|
||||
_initialFiles.append(filename);
|
||||
}
|
||||
|
||||
QStringList GtkFileDialog::selectedFiles() const {
|
||||
// While GtkFileChooserDialog is hidden, gtk_file_chooser_get_filenames()
|
||||
// returns a bogus value -> return the cached value before hiding
|
||||
if (!_selection.isEmpty())
|
||||
return _selection;
|
||||
|
||||
QStringList selection;
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
GSList *filenames = Libs::gtk_file_chooser_get_filenames(Libs::gtk_file_chooser_cast(gtkDialog));
|
||||
for (GSList *it = filenames; it; it = it->next)
|
||||
selection += QString::fromUtf8((const char*)it->data);
|
||||
g_slist_free(filenames);
|
||||
return selection;
|
||||
}
|
||||
|
||||
void GtkFileDialog::setFilter() {
|
||||
applyOptions();
|
||||
}
|
||||
|
||||
void GtkFileDialog::selectNameFilter(const QString &filter) {
|
||||
GtkFileFilter *gtkFilter = _filters.value(filter);
|
||||
if (gtkFilter) {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
Libs::gtk_file_chooser_set_filter(Libs::gtk_file_chooser_cast(gtkDialog), gtkFilter);
|
||||
}
|
||||
}
|
||||
|
||||
QString GtkFileDialog::selectedNameFilter() const {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
GtkFileFilter *gtkFilter = Libs::gtk_file_chooser_get_filter(Libs::gtk_file_chooser_cast(gtkDialog));
|
||||
return _filterNames.value(gtkFilter);
|
||||
}
|
||||
|
||||
void GtkFileDialog::onAccepted() {
|
||||
emit accept();
|
||||
|
||||
// QString filter = selectedNameFilter();
|
||||
// if (filter.isEmpty())
|
||||
// emit filterSelected(filter);
|
||||
|
||||
// QList<QUrl> files = selectedFiles();
|
||||
// emit filesSelected(files);
|
||||
// if (files.count() == 1)
|
||||
// emit fileSelected(files.first());
|
||||
}
|
||||
|
||||
void GtkFileDialog::onRejected() {
|
||||
emit reject();
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
void GtkFileDialog::onSelectionChanged(GtkDialog *gtkDialog, GtkFileDialog *helper) {
|
||||
// QString selection;
|
||||
// gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(gtkDialog));
|
||||
// if (filename) {
|
||||
// selection = QString::fromUtf8(filename);
|
||||
// g_free(filename);
|
||||
// }
|
||||
// emit helper->currentChanged(QUrl::fromLocalFile(selection));
|
||||
}
|
||||
|
||||
void GtkFileDialog::onCurrentFolderChanged(GtkFileDialog *dialog) {
|
||||
// emit dialog->directoryEntered(dialog->directory());
|
||||
}
|
||||
|
||||
GtkFileChooserAction gtkFileChooserAction(QFileDialog::FileMode fileMode, QFileDialog::AcceptMode acceptMode) {
|
||||
switch (fileMode) {
|
||||
case QFileDialog::AnyFile:
|
||||
case QFileDialog::ExistingFile:
|
||||
case QFileDialog::ExistingFiles:
|
||||
if (acceptMode == QFileDialog::AcceptOpen)
|
||||
return GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
else
|
||||
return GTK_FILE_CHOOSER_ACTION_SAVE;
|
||||
case QFileDialog::Directory:
|
||||
default:
|
||||
if (acceptMode == QFileDialog::AcceptOpen)
|
||||
return GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
|
||||
else
|
||||
return GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER;
|
||||
}
|
||||
}
|
||||
|
||||
bool CustomButtonsSupported() {
|
||||
return (Libs::gtk_dialog_get_widget_for_response != nullptr)
|
||||
&& (Libs::gtk_button_set_label != nullptr)
|
||||
&& (Libs::gtk_button_get_type != nullptr);
|
||||
}
|
||||
|
||||
void GtkFileDialog::applyOptions() {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
|
||||
Libs::gtk_window_set_title(Libs::gtk_window_cast(gtkDialog), _windowTitle.toUtf8());
|
||||
Libs::gtk_file_chooser_set_local_only(Libs::gtk_file_chooser_cast(gtkDialog), true);
|
||||
|
||||
const GtkFileChooserAction action = gtkFileChooserAction(_fileMode, _acceptMode);
|
||||
Libs::gtk_file_chooser_set_action(Libs::gtk_file_chooser_cast(gtkDialog), action);
|
||||
|
||||
const bool selectMultiple = (_fileMode == QFileDialog::ExistingFiles);
|
||||
Libs::gtk_file_chooser_set_select_multiple(Libs::gtk_file_chooser_cast(gtkDialog), selectMultiple);
|
||||
|
||||
const bool confirmOverwrite = !_options.testFlag(QFileDialog::DontConfirmOverwrite);
|
||||
Libs::gtk_file_chooser_set_do_overwrite_confirmation(Libs::gtk_file_chooser_cast(gtkDialog), confirmOverwrite);
|
||||
|
||||
if (!_nameFilters.isEmpty())
|
||||
setNameFilters(_nameFilters);
|
||||
|
||||
if (!_initialDirectory.isEmpty())
|
||||
setDirectory(_initialDirectory);
|
||||
|
||||
for_const (const auto &filename, _initialFiles) {
|
||||
if (_acceptMode == QFileDialog::AcceptSave) {
|
||||
QFileInfo fi(filename);
|
||||
Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), fi.path().toUtf8());
|
||||
Libs::gtk_file_chooser_set_current_name(Libs::gtk_file_chooser_cast(gtkDialog), fi.fileName().toUtf8());
|
||||
} else if (filename.endsWith('/')) {
|
||||
Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), filename.toUtf8());
|
||||
} else {
|
||||
Libs::gtk_file_chooser_select_filename(Libs::gtk_file_chooser_cast(gtkDialog), filename.toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
const QString initialNameFilter = _nameFilters.isEmpty() ? QString() : _nameFilters.front();
|
||||
if (!initialNameFilter.isEmpty())
|
||||
selectNameFilter(initialNameFilter);
|
||||
|
||||
if (CustomButtonsSupported()) {
|
||||
GtkWidget *acceptButton = Libs::gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_OK);
|
||||
if (acceptButton) {
|
||||
/*if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept))
|
||||
Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), opts->labelText(QFileDialogOptions::Accept).toUtf8());
|
||||
else*/ if (_acceptMode == QFileDialog::AcceptOpen)
|
||||
Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), tr::lng_open_link(tr::now).toUtf8());
|
||||
else
|
||||
Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), tr::lng_settings_save(tr::now).toUtf8());
|
||||
}
|
||||
|
||||
GtkWidget *rejectButton = Libs::gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_CANCEL);
|
||||
if (rejectButton) {
|
||||
/*if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject))
|
||||
Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), opts->labelText(QFileDialogOptions::Reject).toUtf8());
|
||||
else*/
|
||||
Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), tr::lng_cancel(tr::now).toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GtkFileDialog::setNameFilters(const QStringList &filters) {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
foreach (GtkFileFilter *filter, _filters)
|
||||
Libs::gtk_file_chooser_remove_filter(Libs::gtk_file_chooser_cast(gtkDialog), filter);
|
||||
|
||||
_filters.clear();
|
||||
_filterNames.clear();
|
||||
|
||||
for_const (auto &filter, filters) {
|
||||
GtkFileFilter *gtkFilter = Libs::gtk_file_filter_new();
|
||||
auto name = filter;//.left(filter.indexOf(QLatin1Char('(')));
|
||||
auto extensions = cleanFilterList(filter);
|
||||
|
||||
Libs::gtk_file_filter_set_name(gtkFilter, name.isEmpty() ? extensions.join(QStringLiteral(", ")).toUtf8() : name.toUtf8());
|
||||
for_const (auto &ext, extensions) {
|
||||
auto caseInsensitiveExt = QString();
|
||||
caseInsensitiveExt.reserve(4 * ext.size());
|
||||
for_const (auto ch, ext) {
|
||||
auto chLower = ch.toLower();
|
||||
auto chUpper = ch.toUpper();
|
||||
if (chLower != chUpper) {
|
||||
caseInsensitiveExt.append('[').append(chLower).append(chUpper).append(']');
|
||||
} else {
|
||||
caseInsensitiveExt.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
Libs::gtk_file_filter_add_pattern(gtkFilter, caseInsensitiveExt.toUtf8());
|
||||
}
|
||||
|
||||
Libs::gtk_file_chooser_add_filter(Libs::gtk_file_chooser_cast(gtkDialog), gtkFilter);
|
||||
|
||||
_filters.insert(filter, gtkFilter);
|
||||
_filterNames.insert(gtkFilter, filter);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
} // namespace FileDialog
|
||||
} // namespace Platform
|
||||
|
||||
@@ -9,22 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "platform/platform_file_utilities.h"
|
||||
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
typedef struct _GtkWidget GtkWidget;
|
||||
typedef struct _GtkDialog GtkDialog;
|
||||
typedef struct _GtkFileFilter GtkFileFilter;
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
namespace Platform {
|
||||
namespace File {
|
||||
namespace internal {
|
||||
|
||||
QByteArray EscapeShell(const QByteArray &content);
|
||||
|
||||
} // namespace internal
|
||||
|
||||
inline QString UrlToLocal(const QUrl &url) {
|
||||
return ::File::internal::UrlToLocalDefault(url);
|
||||
@@ -45,114 +31,5 @@ inline void InitLastPath() {
|
||||
::FileDialog::internal::InitLastPathDefault();
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
// This is a patched copy of qgtk2 theme plugin.
|
||||
// We need to use our own gtk file dialog instead of
|
||||
// styling Qt file dialog, because Qt only works with gtk2.
|
||||
// We need to be able to work with gtk2 and gtk3, because
|
||||
// we use gtk3 to work with appindicator3.
|
||||
class QGtkDialog : public QWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QGtkDialog(GtkWidget *gtkWidget);
|
||||
~QGtkDialog();
|
||||
|
||||
GtkDialog *gtkDialog() const;
|
||||
|
||||
void exec();
|
||||
void show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent);
|
||||
void hide();
|
||||
|
||||
signals:
|
||||
void accept();
|
||||
void reject();
|
||||
|
||||
protected:
|
||||
static void onResponse(QGtkDialog *dialog, int response);
|
||||
static void onUpdatePreview(QGtkDialog *dialog);
|
||||
|
||||
private slots:
|
||||
void onParentWindowDestroyed();
|
||||
|
||||
private:
|
||||
GtkWidget *gtkWidget;
|
||||
GtkWidget *_preview = nullptr;
|
||||
|
||||
};
|
||||
|
||||
class GtkFileDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GtkFileDialog(QWidget *parent = Q_NULLPTR,
|
||||
const QString &caption = QString(),
|
||||
const QString &directory = QString(),
|
||||
const QString &filter = QString());
|
||||
~GtkFileDialog();
|
||||
|
||||
void setVisible(bool visible) override;
|
||||
|
||||
void setWindowTitle(const QString &windowTitle) {
|
||||
_windowTitle = windowTitle;
|
||||
}
|
||||
void setAcceptMode(QFileDialog::AcceptMode acceptMode) {
|
||||
_acceptMode = acceptMode;
|
||||
}
|
||||
void setFileMode(QFileDialog::FileMode fileMode) {
|
||||
_fileMode = fileMode;
|
||||
}
|
||||
void setOption(QFileDialog::Option option, bool on = true) {
|
||||
if (on) {
|
||||
_options |= option;
|
||||
} else {
|
||||
_options &= ~option;
|
||||
}
|
||||
}
|
||||
|
||||
int exec() override;
|
||||
|
||||
bool defaultNameFilterDisables() const;
|
||||
void setDirectory(const QString &directory);
|
||||
QDir directory() const;
|
||||
void selectFile(const QString &filename);
|
||||
QStringList selectedFiles() const;
|
||||
void setFilter();
|
||||
void selectNameFilter(const QString &filter);
|
||||
QString selectedNameFilter() const;
|
||||
|
||||
private slots:
|
||||
void onAccepted();
|
||||
void onRejected();
|
||||
|
||||
private:
|
||||
static void onSelectionChanged(GtkDialog *dialog, GtkFileDialog *helper);
|
||||
static void onCurrentFolderChanged(GtkFileDialog *helper);
|
||||
void applyOptions();
|
||||
void setNameFilters(const QStringList &filters);
|
||||
|
||||
void showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent);
|
||||
void hideHelper();
|
||||
|
||||
// Options
|
||||
QFileDialog::Options _options;
|
||||
QString _windowTitle = "Choose file";
|
||||
QString _initialDirectory;
|
||||
QStringList _initialFiles;
|
||||
QStringList _nameFilters;
|
||||
QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen;
|
||||
QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile;
|
||||
|
||||
QString _dir;
|
||||
QStringList _selection;
|
||||
QHash<QString, GtkFileFilter*> _filters;
|
||||
QHash<GtkFileFilter*, QString> _filterNames;
|
||||
QScopedPointer<QGtkDialog> d;
|
||||
};
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
} // namespace internal
|
||||
} // namespace FileDialog
|
||||
} // namespace Platform
|
||||
|
||||
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/launcher_linux.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/linux/specific_linux.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/update_checker.h"
|
||||
@@ -46,7 +45,7 @@ private:
|
||||
} // namespace
|
||||
|
||||
Launcher::Launcher(int argc, char *argv[])
|
||||
: Core::Launcher(argc, argv, DeviceModelPretty(), SystemVersionPretty()) {
|
||||
: Core::Launcher(argc, argv) {
|
||||
}
|
||||
|
||||
void Launcher::initHook() {
|
||||
|
||||
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/linux_desktop_environment.h"
|
||||
|
||||
#include "platform/linux/specific_linux.h"
|
||||
#include "base/qt_adapters.h"
|
||||
|
||||
namespace Platform {
|
||||
|
||||
@@ -5,12 +5,9 @@ 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
|
||||
*/
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
#include "platform/linux/linux_gdk_helper.h"
|
||||
|
||||
#include "platform/linux/linux_libs.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/linux/base_xcb_utilities_linux.h"
|
||||
#include "platform/linux/linux_gtk_integration_p.h"
|
||||
|
||||
extern "C" {
|
||||
#undef signals
|
||||
@@ -21,6 +18,8 @@ extern "C" {
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
using namespace Platform::Gtk;
|
||||
|
||||
enum class GtkLoaded {
|
||||
GtkNone,
|
||||
Gtk2,
|
||||
@@ -33,6 +32,9 @@ GtkLoaded gdk_helper_loaded = GtkLoaded::GtkNone;
|
||||
#define GdkDrawable GdkWindow
|
||||
|
||||
// Gtk 2
|
||||
using f_gdk_x11_drawable_get_xdisplay = Display*(*)(GdkDrawable*);
|
||||
f_gdk_x11_drawable_get_xdisplay gdk_x11_drawable_get_xdisplay = nullptr;
|
||||
|
||||
using f_gdk_x11_drawable_get_xid = XID(*)(GdkDrawable*);
|
||||
f_gdk_x11_drawable_get_xid gdk_x11_drawable_get_xid = nullptr;
|
||||
|
||||
@@ -43,24 +45,33 @@ f_gdk_x11_window_get_type gdk_x11_window_get_type = nullptr;
|
||||
// To be able to compile with gtk-2.0 headers as well
|
||||
template <typename Object>
|
||||
inline bool gdk_is_x11_window_check(Object *obj) {
|
||||
return Libs::g_type_cit_helper(obj, gdk_x11_window_get_type());
|
||||
return g_type_cit_helper(obj, gdk_x11_window_get_type());
|
||||
}
|
||||
|
||||
using f_gdk_window_get_display = GdkDisplay*(*)(GdkWindow *window);
|
||||
f_gdk_window_get_display gdk_window_get_display = nullptr;
|
||||
|
||||
using f_gdk_x11_display_get_xdisplay = Display*(*)(GdkDisplay *display);
|
||||
f_gdk_x11_display_get_xdisplay gdk_x11_display_get_xdisplay = nullptr;
|
||||
|
||||
using f_gdk_x11_window_get_xid = Window(*)(GdkWindow *window);
|
||||
f_gdk_x11_window_get_xid gdk_x11_window_get_xid = nullptr;
|
||||
|
||||
bool GdkHelperLoadGtk2(QLibrary &lib) {
|
||||
#if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
#ifdef LINK_TO_GTK
|
||||
return false;
|
||||
#else // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
if (!LOAD_SYMBOL(lib, "gdk_x11_drawable_get_xid", gdk_x11_drawable_get_xid)) return false;
|
||||
#else // LINK_TO_GTK
|
||||
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_drawable_get_xdisplay", gdk_x11_drawable_get_xdisplay)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_drawable_get_xid", gdk_x11_drawable_get_xid)) return false;
|
||||
return true;
|
||||
#endif // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY
|
||||
#endif // !LINK_TO_GTK
|
||||
}
|
||||
|
||||
bool GdkHelperLoadGtk3(QLibrary &lib) {
|
||||
if (!LOAD_SYMBOL(lib, "gdk_x11_window_get_type", gdk_x11_window_get_type)) return false;
|
||||
if (!LOAD_SYMBOL(lib, "gdk_x11_window_get_xid", gdk_x11_window_get_xid)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_window_get_type", gdk_x11_window_get_type)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib, "gdk_window_get_display", gdk_window_get_display)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_display_get_xdisplay", gdk_x11_display_get_xdisplay)) return false;
|
||||
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_window_get_xid", gdk_x11_window_get_xid)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -79,32 +90,17 @@ bool GdkHelperLoaded() {
|
||||
|
||||
void XSetTransientForHint(GdkWindow *window, quintptr winId) {
|
||||
if (gdk_helper_loaded == GtkLoaded::Gtk2) {
|
||||
if (!IsWayland()) {
|
||||
xcb_change_property(
|
||||
base::Platform::XCB::GetConnectionFromQt(),
|
||||
XCB_PROP_MODE_REPLACE,
|
||||
gdk_x11_drawable_get_xid(window),
|
||||
XCB_ATOM_WM_TRANSIENT_FOR,
|
||||
XCB_ATOM_WINDOW,
|
||||
32,
|
||||
1,
|
||||
&winId);
|
||||
}
|
||||
::XSetTransientForHint(gdk_x11_drawable_get_xdisplay(window),
|
||||
gdk_x11_drawable_get_xid(window),
|
||||
winId);
|
||||
} else if (gdk_helper_loaded == GtkLoaded::Gtk3) {
|
||||
if (!IsWayland() && gdk_is_x11_window_check(window)) {
|
||||
xcb_change_property(
|
||||
base::Platform::XCB::GetConnectionFromQt(),
|
||||
XCB_PROP_MODE_REPLACE,
|
||||
gdk_x11_window_get_xid(window),
|
||||
XCB_ATOM_WM_TRANSIENT_FOR,
|
||||
XCB_ATOM_WINDOW,
|
||||
32,
|
||||
1,
|
||||
&winId);
|
||||
if (gdk_is_x11_window_check(window)) {
|
||||
::XSetTransientForHint(gdk_x11_display_get_xdisplay(gdk_window_get_display(window)),
|
||||
gdk_x11_window_get_xid(window),
|
||||
winId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
@@ -7,11 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
class QLibrary;
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
extern "C" {
|
||||
#undef signals
|
||||
#include <gtk/gtk.h>
|
||||
@@ -28,4 +25,3 @@ void XSetTransientForHint(GdkWindow *window, quintptr winId);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
|
||||
|
||||
181
Telegram/SourceFiles/platform/linux/linux_gsd_media_keys.cpp
Normal file
181
Telegram/SourceFiles/platform/linux/linux_gsd_media_keys.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
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/linux_gsd_media_keys.h"
|
||||
|
||||
#include "core/sandbox.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
|
||||
#include <QtDBus/QDBusConnection>
|
||||
#include <QtDBus/QDBusConnectionInterface>
|
||||
|
||||
extern "C" {
|
||||
#undef signals
|
||||
#include <gio/gio.h>
|
||||
#define signals public
|
||||
} // extern "C"
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
namespace {
|
||||
|
||||
constexpr auto kService = "org.gnome.SettingsDaemon.MediaKeys"_cs;
|
||||
constexpr auto kOldService = "org.gnome.SettingsDaemon"_cs;
|
||||
constexpr auto kMATEService = "org.mate.SettingsDaemon"_cs;
|
||||
constexpr auto kObjectPath = "/org/gnome/SettingsDaemon/MediaKeys"_cs;
|
||||
constexpr auto kMATEObjectPath = "/org/mate/SettingsDaemon/MediaKeys"_cs;
|
||||
constexpr auto kInterface = kService;
|
||||
constexpr auto kMATEInterface = "org.mate.SettingsDaemon.MediaKeys"_cs;
|
||||
|
||||
void KeyPressed(
|
||||
GDBusConnection *connection,
|
||||
const gchar *sender_name,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *signal_name,
|
||||
GVariant *parameters,
|
||||
gpointer user_data) {
|
||||
gchar *appUtf8;
|
||||
gchar *keyUtf8;
|
||||
g_variant_get(parameters, "(ss)", &appUtf8, &keyUtf8);
|
||||
const auto app = QString::fromUtf8(appUtf8);
|
||||
const auto key = QString::fromUtf8(keyUtf8);
|
||||
g_free(keyUtf8);
|
||||
g_free(appUtf8);
|
||||
|
||||
if (app != QCoreApplication::applicationName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
if (key == qstr("Play")) {
|
||||
Media::Player::instance()->playPause();
|
||||
} else if (key == qstr("Stop")) {
|
||||
Media::Player::instance()->stop();
|
||||
} else if (key == qstr("Next")) {
|
||||
Media::Player::instance()->next();
|
||||
} else if (key == qstr("Previous")) {
|
||||
Media::Player::instance()->previous();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GSDMediaKeys::GSDMediaKeys() {
|
||||
GError *error = nullptr;
|
||||
const auto interface = QDBusConnection::sessionBus().interface();
|
||||
|
||||
if (!interface) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (interface->isServiceRegistered(kService.utf16())) {
|
||||
_service = kService.utf16();
|
||||
_objectPath = kObjectPath.utf16();
|
||||
_interface = kInterface.utf16();
|
||||
} else if (interface->isServiceRegistered(kOldService.utf16())) {
|
||||
_service = kOldService.utf16();
|
||||
_objectPath = kObjectPath.utf16();
|
||||
_interface = kInterface.utf16();
|
||||
} else if (interface->isServiceRegistered(kMATEService.utf16())) {
|
||||
_service = kMATEService.utf16();
|
||||
_objectPath = kMATEObjectPath.utf16();
|
||||
_interface = kMATEInterface.utf16();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
_dbusConnection = g_bus_get_sync(
|
||||
G_BUS_TYPE_SESSION,
|
||||
nullptr,
|
||||
&error);
|
||||
|
||||
if (error) {
|
||||
LOG(("GSD Media Keys Error: %1").arg(error->message));
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
auto reply = g_dbus_connection_call_sync(
|
||||
_dbusConnection,
|
||||
_service.toUtf8(),
|
||||
_objectPath.toUtf8(),
|
||||
_interface.toUtf8(),
|
||||
"GrabMediaPlayerKeys",
|
||||
g_variant_new(
|
||||
"(su)",
|
||||
QCoreApplication::applicationName().toUtf8().constData(),
|
||||
0),
|
||||
nullptr,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
nullptr,
|
||||
&error);
|
||||
|
||||
if (!error) {
|
||||
_grabbed = true;
|
||||
g_variant_unref(reply);
|
||||
} else {
|
||||
LOG(("GSD Media Keys Error: %1").arg(error->message));
|
||||
g_error_free(error);
|
||||
}
|
||||
|
||||
_signalId = g_dbus_connection_signal_subscribe(
|
||||
_dbusConnection,
|
||||
_service.toUtf8(),
|
||||
_interface.toUtf8(),
|
||||
"MediaPlayerKeyPressed",
|
||||
_objectPath.toUtf8(),
|
||||
nullptr,
|
||||
G_DBUS_SIGNAL_FLAGS_NONE,
|
||||
KeyPressed,
|
||||
nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
GSDMediaKeys::~GSDMediaKeys() {
|
||||
GError *error = nullptr;
|
||||
|
||||
if (_signalId != 0) {
|
||||
g_dbus_connection_signal_unsubscribe(
|
||||
_dbusConnection,
|
||||
_signalId);
|
||||
}
|
||||
|
||||
if (_grabbed) {
|
||||
auto reply = g_dbus_connection_call_sync(
|
||||
_dbusConnection,
|
||||
_service.toUtf8(),
|
||||
_objectPath.toUtf8(),
|
||||
_interface.toUtf8(),
|
||||
"ReleaseMediaPlayerKeys",
|
||||
g_variant_new(
|
||||
"(s)",
|
||||
QCoreApplication::applicationName().toUtf8().constData()),
|
||||
nullptr,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
nullptr,
|
||||
&error);
|
||||
|
||||
if (!error) {
|
||||
_grabbed = false;
|
||||
g_variant_unref(reply);
|
||||
} else {
|
||||
LOG(("GSD Media Keys Error: %1").arg(error->message));
|
||||
g_error_free(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (_dbusConnection) {
|
||||
g_object_unref(_dbusConnection);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
36
Telegram/SourceFiles/platform/linux/linux_gsd_media_keys.h
Normal file
36
Telegram/SourceFiles/platform/linux/linux_gsd_media_keys.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
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
|
||||
|
||||
typedef struct _GDBusConnection GDBusConnection;
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
class GSDMediaKeys {
|
||||
public:
|
||||
GSDMediaKeys();
|
||||
|
||||
GSDMediaKeys(const GSDMediaKeys &other) = delete;
|
||||
GSDMediaKeys &operator=(const GSDMediaKeys &other) = delete;
|
||||
GSDMediaKeys(GSDMediaKeys &&other) = delete;
|
||||
GSDMediaKeys &operator=(GSDMediaKeys &&other) = delete;
|
||||
|
||||
~GSDMediaKeys();
|
||||
|
||||
private:
|
||||
GDBusConnection *_dbusConnection = nullptr;
|
||||
QString _service;
|
||||
QString _objectPath;
|
||||
QString _interface;
|
||||
uint _signalId = 0;
|
||||
bool _grabbed = false;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user