diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 83% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md index f906731af4..ff3c0ad1e2 100644 --- a/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -11,6 +11,7 @@ This document describes how you can contribute to Telegram Desktop. Please read * [Pull upstream changes into your fork regularly](#pull-upstream-changes-into-your-fork-regularly) * [How to get your pull request accepted](#how-to-get-your-pull-request-accepted) * [Keep your pull requests limited to a single issue](#keep-your-pull-requests-limited-to-a-single-issue) + * [Squash your commits to a single commit](#squash-your-commits-to-a-single-commit) * [Don't mix code changes with whitespace cleanup](#dont-mix-code-changes-with-whitespace-cleanup) * [Keep your code simple!](#keep-your-code-simple) * [Test your changes!](#test-your-changes) @@ -72,10 +73,8 @@ For more info, see [GitHub Help][help_change_commit_message]. ## Build instructions -* [Visual Studio 2013][msvc] -* [XCode 6.4][xcode] -* [XCode 6.4 for OS X 10.6 and 10.7][xcode_old] -* [Qt Creator 3.2.0 Ubuntu][qtcreator] +See the [README.md](README.md#build-instructions) for details on the various build +environments. ## Pull upstream changes into your fork regularly @@ -90,9 +89,13 @@ Check the log to be sure that you actually want the changes, before merging: git log upstream/master -Then merge the changes that you fetched: +Then rebase your changes on the latest commits in the `master` branch: - git merge upstream/master + git rebase upstream/master + +After that, you have to force push your commits: + + git push --force For more info, see [GitHub Help][help_fork_repo]. @@ -107,6 +110,21 @@ Pull requests should be as small/atomic as possible. Large, wide-sweeping change * If you are making spelling corrections in the docs, don't modify other files. * If you are adding new functions don't '*cleanup*' unrelated functions. That cleanup belongs in another pull request. +#### Squash your commits to a single commit + +To keep the history of the project clean, you should make one commit per pull request. +If you already have multiple commits, you can add the commits together (squash them) with the following commands in Git Bash: + +1. Open `Git Bash` (or `Git Shell`) +2. Enter following command to squash the recent {N} commits: `git reset --soft HEAD~{N} && git commit` (replace `{N}` with the number of commits you want to squash) +3. Press i to get into Insert-mode +4. Enter the commit message of the new commit (and add the [signature](#sign-your-work) at the and) +5. After adding the message, press ESC to get out of the Insert-mode +6. Write `:wq` and press Enter to save the new message or write `:q!` to discard your changes +7. Enter `git push --force` to push the new commit to the remote repository + +For example, if you want to squash the last 5 commits, use `git reset --soft HEAD~5 && git commit` + ### Don't mix code changes with whitespace cleanup If you change two lines of code and correct 200 lines of whitespace issues in a file the diff on that pull request is functionally unreadable and will be **rejected**. Whitespace cleanups need to be in their own pull request. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..42a39d90b9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,26 @@ + +### Steps to reproduce +1. +2. +3. + +### Expected behaviour +Tell us what should happen + +### Actual behaviour +Tell us what happens instead + +### Configuration +**Operating system:** + +**Version of Telegram Desktop:** + +### Logs +Insert logs here (if necessary) + +You can type "debugmode" in settings and then see ~/.TelegramDesktop/DebugLogs/log_...txt for log files. +Type "debugmode" in settings again to disable logs. diff --git a/.gitignore b/.gitignore index 19c18e0cfb..a155d81c70 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,11 @@ /Telegram/SourceFiles/art/sprite_125x.png /Telegram/SourceFiles/art/sprite_150x.png /Telegram/*.user +*.vcxproj.user *.suo *.sdf *.opensdf +*.opendb /Telegram/*.aps /Win32/ ipch/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..11f305e48e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,47 @@ +sudo: required + +language: cpp + +env: + - BUILD_VERSION="" + - BUILD_VERSION="disable_autoupdate" + - BUILD_VERSION="disable_register_custom_scheme" + - BUILD_VERSION="disable_crash_reports" + - BUILD_VERSION="disable_network_proxy" + +arch: + packages: + - bzr + - wget + - qt5-base + + - git + - patch + - libunity + - libappindicator-gtk2 + + - ffmpeg + - icu + - jasper + - libexif + - libmng + - libwebp + - libxkbcommon-x11 + - libinput + - libproxy + - mtdev + - openal + - libva + - desktop-file-utils + - gtk-update-icon-cache + + script: + - libtool --finish /usr/lib + - .travis/build.sh + +before_install: + - "export TRAVIS_COMMIT_MSG=\"$(git log --format=%B --no-merges -n 1)\"" + - .travis/check.sh + +script: + - .travis/arch.sh \ No newline at end of file diff --git a/.travis/arch.sh b/.travis/arch.sh new file mode 100755 index 0000000000..9c917be1d1 --- /dev/null +++ b/.travis/arch.sh @@ -0,0 +1,297 @@ +#!/bin/bash +# Copyright (C) 2016 Mikkel Oscar Lyderik Larsen +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Source: https://raw.githubusercontent.com/mikkeloscar/arch-travis/master/arch-travis.sh + +# Script for setting up and running a travis-ci build in an up to date +# Arch Linux chroot + +ARCH_TRAVIS_MIRROR=${ARCH_TRAVIS_MIRROR:-"https://lug.mtu.edu/archlinux"} +ARCH_TRAVIS_ARCH_ISO=${ARCH_TRAVIS_ARCH_ISO:-"$(date +%Y.%m).01"} +mirror_entry='Server = '$ARCH_TRAVIS_MIRROR'/\$repo/os/\$arch' +archive="archlinux-bootstrap-$ARCH_TRAVIS_ARCH_ISO-x86_64.tar.gz" +default_root="root.x86_64" +ARCH_TRAVIS_CHROOT=${ARCH_TRAVIS_CHROOT:-"$default_root"} +user="travis" +user_home="/home/$user" +user_build_dir="/build" +user_uid=$UID + +if [ -n "$CC" ]; then + # store travis CC + TRAVIS_CC=$CC + # reset to gcc for building arch packages + CC=gcc +fi + + +# default packages +default_packages=("base-devel" "git") + +# pacman.conf repository line +repo_line=70 + +# setup working Arch Linux chroot +setup_chroot() { + arch_msg "Setting up Arch chroot" + + if [ ! -f $archive ]; then + # get root fs + curl --fail -O "$ARCH_TRAVIS_MIRROR/iso/$ARCH_TRAVIS_ARCH_ISO/$archive" 2>&1 + local ret=$? + + # if it fails, try arch iso form the previous month + if [ $ret -gt 0 ]; then + ARCH_TRAVIS_ARCH_ISO="$(date +%Y.%m -d "-1 month").01" + archive="archlinux-bootstrap-$ARCH_TRAVIS_ARCH_ISO-x86_64.tar.gz" + as_normal "curl -O $ARCH_TRAVIS_MIRROR/iso/$ARCH_TRAVIS_ARCH_ISO/$archive" + fi + fi + + # extract root fs + as_root "tar xf $archive" + + # remove archive if ARCH_TRAVIS_CLEAN_CHROOT is set + if [ -n "$ARCH_TRAVIS_CLEAN_CHROOT" ]; then + as_root "rm $archive" + fi + + if [ "$ARCH_TRAVIS_CHROOT" != "$default_root" ]; then + as_root "mv $default_root $ARCH_TRAVIS_CHROOT" + fi + + # don't care for signed packages + as_root "sed -i 's|SigLevel = Required DatabaseOptional|SigLevel = Never|' $ARCH_TRAVIS_CHROOT/etc/pacman.conf" + + # enable multilib + as_root "sed -i 's|#\[multilib\]|\[multilib\]\nInclude = /etc/pacman.d/mirrorlist|' $ARCH_TRAVIS_CHROOT/etc/pacman.conf" + + # add mirror + as_root "echo $mirror_entry >> $ARCH_TRAVIS_CHROOT/etc/pacman.d/mirrorlist" + + # add nameserver to resolv.conf + as_root "echo nameserver 8.8.8.8 >> $ARCH_TRAVIS_CHROOT/etc/resolv.conf" + + sudo mount $ARCH_TRAVIS_CHROOT $ARCH_TRAVIS_CHROOT --bind + sudo mount --bind /proc $ARCH_TRAVIS_CHROOT/proc + sudo mount --bind /sys $ARCH_TRAVIS_CHROOT/sys + sudo mount --bind /dev $ARCH_TRAVIS_CHROOT/dev + sudo mount --bind /dev/pts $ARCH_TRAVIS_CHROOT/dev/pts + sudo mount --bind /dev/shm $ARCH_TRAVIS_CHROOT/dev/shm + sudo mount --bind /run $ARCH_TRAVIS_CHROOT/run + + # update packages + chroot_as_root "pacman -Syy" + chroot_as_root "pacman -Syu ${default_packages[*]} --noconfirm" + + # use LANG=en_US.UTF-8 as expected in travis environments + as_root "sed -i 's|#en_US.UTF-8|en_US.UTF-8|' $ARCH_TRAVIS_CHROOT/etc/locale.gen" + chroot_as_root "locale-gen" + + # setup non-root user + chroot_as_root "useradd -u $user_uid -m -s /bin/bash $user" + + # disable password for sudo users + as_root "echo \"$user ALL=(ALL) NOPASSWD: ALL\" >> $ARCH_TRAVIS_CHROOT/etc/sudoers.d/$user" + + # Add build dir + chroot_as_root "mkdir $user_build_dir && chown $user $user_build_dir" + + # bind $TRAVIS_BUILD_DIR to chroot build dir + sudo mount --bind $TRAVIS_BUILD_DIR $ARCH_TRAVIS_CHROOT$user_build_dir + + # add custom repos + add_repositories + + # setup pacaur for AUR packages + setup_pacaur +} + +# add custom repositories to pacman.conf +add_repositories() { + if [ ${#CONFIG_REPOS[@]} -gt 0 ]; then + for r in "${CONFIG_REPOS[@]}"; do + local splitarr=(${r//=/ }) + ((repo_line+=1)) + as_root "sed -i '${repo_line}i[${splitarr[0]}]' $ARCH_TRAVIS_CHROOT/etc/pacman.conf" + ((repo_line+=1)) + as_root "sed -i '${repo_line}iServer = ${splitarr[1]}\n' $ARCH_TRAVIS_CHROOT/etc/pacman.conf" + ((repo_line+=1)) + done + + # update repos + chroot_as_root "pacman -Syy" + fi +} + +# a wrapper which can be used to eventually add fakeroot support. +sudo_wrapper() { + sudo "$@" +} + +# run command as normal user +as_normal() { + local str="$@" + run /bin/bash -c "$str" +} + +# run command as root +as_root() { + local str="$@" + run sudo_wrapper /bin/bash -c "$str" +} + +# run command in chroot as root +chroot_as_root() { + local str="$@" + run sudo_wrapper chroot $ARCH_TRAVIS_CHROOT /bin/bash -c "$str" +} + +# run command in chroot as normal user +chroot_as_normal() { + local str="$@" + run sudo_wrapper chroot --userspec=$user:$user $ARCH_TRAVIS_CHROOT /bin/bash \ + -c "export HOME=$user_home USER=$user TRAVIS_BUILD_DIR=$user_build_dir && cd $user_build_dir && $str" +} + +# run command +run() { + "$@" + local ret=$? + + if [ $ret -gt 0 ]; then + takedown_chroot + exit $ret + fi +} + +# run build script +run_build_script() { + local cmd="$@" + echo "$ $cmd" + sudo_wrapper chroot --userspec=$user:$user $ARCH_TRAVIS_CHROOT /bin/bash -c "export HOME=$user_home USER=$user TRAVIS_BUILD_DIR=$user_build_dir && cd $user_build_dir && $cmd" + local ret=$? + + if [ $ret -gt 0 ]; then + takedown_chroot + exit $ret + fi +} + +# setup pacaur +setup_pacaur() { + local cowerarchive="cower.tar.gz" + local aururl="https://aur.archlinux.org/cgit/aur.git/snapshot/" + # install cower + as_normal "curl -O $aururl/$cowerarchive" + as_normal "tar xf $cowerarchive" + chroot_as_normal "cd cower && makepkg -is --skippgpcheck --noconfirm" + as_root "rm -r cower" + as_normal "rm $cowerarchive" + # install pacaur + chroot_as_normal "cower -dd pacaur" + chroot_as_normal "cd pacaur && makepkg -is --noconfirm" + chroot_as_normal "rm -rf pacaur" +} + +# install package through pacaur +_pacaur() { + local pacaur="pacaur -S $@ --noconfirm --noedit" + chroot_as_normal "$pacaur" +} + +# takedown chroot +# unmounts anything mounted in the chroot setup +takedown_chroot() { + sudo umount $ARCH_TRAVIS_CHROOT/{run,dev/shm,dev/pts,dev,sys,proc} + sudo umount $ARCH_TRAVIS_CHROOT$user_build_dir + sudo umount $ARCH_TRAVIS_CHROOT + + if [ -n "$ARCH_TRAVIS_CLEAN_CHROOT" ]; then + as_root "rm -rf $ARCH_TRAVIS_CHROOT" + fi +} + +# read value from .travis.yml +travis_yml() { + ruby -ryaml -e 'puts ARGV[1..-1].inject(YAML.load(File.read(ARGV[0]))) {|acc, key| acc[key] }' .travis.yml $@ +} + +read_config() { + old_ifs=$IFS + IFS=$'\n' + CONFIG_BUILD_SCRIPTS=($(travis_yml arch script)) + CONFIG_PACKAGES=($(travis_yml arch packages)) + CONFIG_REPOS=($(travis_yml arch repos)) + IFS=$old_ifs +} + +# run build scripts defined in .travis.yml +build_scripts() { + if [ ${#CONFIG_BUILD_SCRIPTS[@]} -gt 0 ]; then + for script in "${CONFIG_BUILD_SCRIPTS[@]}"; do + run_build_script $script + done + else + echo "No build scripts defined" + takedown_chroot + exit 1 + fi +} + +# install packages defined in .travis.yml +install_packages() { + for package in "${CONFIG_PACKAGES[@]}"; do + _pacaur $package + done +} + +# install custom compiler if CC != gcc +install_c_compiler() { + if [ "$TRAVIS_CC" != "gcc" ]; then + _pacaur "$TRAVIS_CC" + fi +} + +arch_msg() { + lightblue='\033[1;34m' + reset='\e[0m' + echo -e "${lightblue}$@${reset}" +} + +# read .travis.yml +read_config + +echo "travis_fold:start:arch_travis" +setup_chroot + +install_packages + +if [ -n "$CC" ]; then + install_c_compiler + + # restore CC + CC=$TRAVIS_CC +fi +echo "travis_fold:end:arch_travis" +echo "" + +arch_msg "Running travis build" +build_scripts + +takedown_chroot + +# vim:set ts=2 sw=2 et: diff --git a/.travis/build.sh b/.travis/build.sh new file mode 100755 index 0000000000..7deae1f953 --- /dev/null +++ b/.travis/build.sh @@ -0,0 +1,160 @@ +#!/bin/bash +# Installs libs and compiles tdesktop + +run() { + info_msg "Build version: ${BUILD_VERSION}" + + downloadLibs + prepare + build + check +} + +downloadLibs() { + travis_fold_start "download_libs" + # Move telegram project to subfolder + mkdir tdesktop + mv -f Telegram tdesktop + + # Download libraries + info_msg "QT-Version: ${_qtver}, SRC-Dir: ${srcdir}" + + echo -e "\nDownload and extract qt" + qt_file=qt-everywhere-opensource-src-$_qtver.tar.xz + echo -e "QT-File: ${qt_file}" + + wget "http://download.qt.io/official_releases/qt/${_qtver%.*}/$_qtver/single/$qt_file" + tar xf $qt_file + rm $qt_file + + echo -e "Clone Breakpad" + git clone https://chromium.googlesource.com/breakpad/breakpad breakpad + + echo -e "\nClone Linux Syscall Support" + git clone https://chromium.googlesource.com/linux-syscall-support breakpad-lss + + echo -e "\nLets view the folder content" + ls + travis_fold_end "download_libs" +} + +prepare() { + travis_fold_start "prepare" + start_msg "Preparing the libraries..." + + cd "$srcdir/tdesktop" + + mkdir -p "$srcdir/Libraries" + + local qt_patch_file="$srcdir/tdesktop/Telegram/_qtbase_${_qtver//./_}_patch.diff" + if [ "$qt_patch_file" -nt "$srcdir/Libraries/QtStatic" ]; then + rm -rf "$srcdir/Libraries/QtStatic" + mv "$srcdir/qt-everywhere-opensource-src-$_qtver" "$srcdir/Libraries/QtStatic" + cd "$srcdir/Libraries/QtStatic/qtbase" + patch -p1 -i "$qt_patch_file" + fi + + if [ ! -h "$srcdir/Libraries/breakpad" ]; then + ln -s "$srcdir/breakpad" "$srcdir/Libraries/breakpad" + ln -s "$srcdir/breakpad-lss" "$srcdir/Libraries/breakpad/src/third_party/lss" + fi + + sed -i 's/CUSTOM_API_ID//g' "$srcdir/tdesktop/Telegram/Telegram.pro" + sed -i 's,LIBS += /usr/local/lib/libxkbcommon.a,,g' "$srcdir/tdesktop/Telegram/Telegram.pro" + sed -i 's,LIBS += /usr/local/lib/libz.a,LIBS += -lz,g' "$srcdir/tdesktop/Telegram/Telegram.pro" + + local options="" + + if [[ $BUILD_VERSION == *"disable_autoupdate"* ]]; then + options+="\nDEFINES += TDESKTOP_DISABLE_AUTOUPDATE" + fi + + if [[ $BUILD_VERSION == *"disable_register_custom_scheme"* ]]; then + options+="\nDEFINES += TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME" + fi + + if [[ $BUILD_VERSION == *"disable_crash_reports"* ]]; then + options+="\nDEFINES += TDESKTOP_DISABLE_CRASH_REPORTS" + fi + + if [[ $BUILD_VERSION == *"disable_network_proxy"* ]]; then + options+="\nDEFINES += TDESKTOP_DISABLE_NETWORK_PROXY" + fi + + options+='\nINCLUDEPATH += "/usr/lib/glib-2.0/include"' + options+='\nINCLUDEPATH += "/usr/lib/gtk-2.0/include"' + options+='\nINCLUDEPATH += "/usr/include/opus"' + options+='\nLIBS += -lcrypto -lssl' + + info_msg "Build options: ${options}" + + echo -e "${options}" >> "$srcdir/tdesktop/Telegram/Telegram.pro" + + success_msg "Prepare done! :)" + travis_fold_end "prepare" +} + +build() { + start_msg "Building the projects..." + + info_msg "Build patched Qt" + # Build patched Qt + cd "$srcdir/Libraries/QtStatic" + ./configure -prefix "$srcdir/qt" -release -opensource -confirm-license -qt-zlib \ + -qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz -qt-pcre -qt-xcb \ + -qt-xkbcommon-x11 -no-opengl -static -nomake examples -nomake tests + make --silent module-qtbase module-qtimageformats + make --silent module-qtbase-install_subtargets module-qtimageformats-install_subtargets + + export PATH="$srcdir/qt/bin:$PATH" + + info_msg "Build breakpad" + # Build breakpad + cd "$srcdir/Libraries/breakpad" + ./configure + make --silent + + info_msg "Build MetaStyle" + # Build MetaStyle + mkdir -p "$srcdir/tdesktop/Linux/DebugIntermediateStyle" + cd "$srcdir/tdesktop/Linux/DebugIntermediateStyle" + qmake CONFIG+=debug "../../Telegram/MetaStyle.pro" + make --silent + + info_msg "Build MetaLang" + # Build MetaLang + mkdir -p "$srcdir/tdesktop/Linux/DebugIntermediateLang" + cd "$srcdir/tdesktop/Linux/DebugIntermediateLang" + qmake CONFIG+=debug "../../Telegram/MetaLang.pro" + make --silent + + info_msg "Build Telegram Desktop" + # Build Telegram Desktop + mkdir -p "$srcdir/tdesktop/Linux/ReleaseIntermediate" + cd "$srcdir/tdesktop/Linux/ReleaseIntermediate" + + qmake CONFIG+=release "../../Telegram/Telegram.pro" + local pattern="^PRE_TARGETDEPS +=" + grep "$pattern" "$srcdir/tdesktop/Telegram/Telegram.pro" | sed "s/$pattern//g" | xargs make + + qmake CONFIG+=release "../../Telegram/Telegram.pro" + make +} + +check() { + local filePath="$srcdir/tdesktop/Linux/Release/Telegram" + if test -f "$filePath"; then + success_msg "Build successful done! :)" + + local size; + size=$(stat -c %s "$filePath") + success_msg "File size of ${filePath}: ${size} Bytes" + else + error_msg "Build error, output file does not exist" + exit 1 + fi +} + +source ./.travis/common.sh + +run diff --git a/.travis/check.sh b/.travis/check.sh new file mode 100755 index 0000000000..3fe7c7fd3c --- /dev/null +++ b/.travis/check.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Checks commit message, ... + +run() { + checkCommitMessage +} + +checkCommitMessage() { + info_msg "Commit message: ${TRAVIS_COMMIT_MSG}"; + info_msg "Is pull request: ${TRAVIS_PULL_REQUEST}"; + + if [[ $TRAVIS_PULL_REQUEST != "false" ]];then + if [[ $TRAVIS_COMMIT_MSG != *"Signed-off-by: "* ]];then + error_msg "The commit message does not contain the signature!" + error_msg "More information: https://github.com/telegramdesktop/tdesktop/blob/master/.github/CONTRIBUTING.md#sign-your-work" + exit 1 + else + success_msg "Commit message contains signature" + fi + fi +} + +source ./.travis/common.sh + +run diff --git a/.travis/common.sh b/.travis/common.sh new file mode 100755 index 0000000000..dd3bb19673 --- /dev/null +++ b/.travis/common.sh @@ -0,0 +1,40 @@ +# set colors +RCol='\e[0m' # Text Reset + +# Regular Bold Underline High Intensity BoldHigh Intens Background High Intensity Backgrounds +Bla='\e[0;30m'; BBla='\e[1;30m'; UBla='\e[4;30m'; IBla='\e[0;90m'; BIBla='\e[1;90m'; On_Bla='\e[40m'; On_IBla='\e[0;100m'; +Red='\e[0;31m'; BRed='\e[1;31m'; URed='\e[4;31m'; IRed='\e[0;91m'; BIRed='\e[1;91m'; On_Red='\e[41m'; On_IRed='\e[0;101m'; +Gre='\e[0;32m'; BGre='\e[1;32m'; UGre='\e[4;32m'; IGre='\e[0;92m'; BIGre='\e[1;92m'; On_Gre='\e[42m'; On_IGre='\e[0;102m'; +Yel='\e[0;33m'; BYel='\e[1;33m'; UYel='\e[4;33m'; IYel='\e[0;93m'; BIYel='\e[1;93m'; On_Yel='\e[43m'; On_IYel='\e[0;103m'; +Blu='\e[0;34m'; BBlu='\e[1;34m'; UBlu='\e[4;34m'; IBlu='\e[0;94m'; BIBlu='\e[1;94m'; On_Blu='\e[44m'; On_IBlu='\e[0;104m'; +Pur='\e[0;35m'; BPur='\e[1;35m'; UPur='\e[4;35m'; IPur='\e[0;95m'; BIPur='\e[1;95m'; On_Pur='\e[45m'; On_IPur='\e[0;105m'; +Cya='\e[0;36m'; BCya='\e[1;36m'; UCya='\e[4;36m'; ICya='\e[0;96m'; BICya='\e[1;96m'; On_Cya='\e[46m'; On_ICya='\e[0;106m'; +Whi='\e[0;37m'; BWhi='\e[1;37m'; UWhi='\e[4;37m'; IWhi='\e[0;97m'; BIWhi='\e[1;97m'; On_Whi='\e[47m'; On_IWhi='\e[0;107m'; + +# Set variables +_qtver=5.5.1 +srcdir=${PWD} + +start_msg() { + echo -e "\n${Gre}$*${RCol}" +} + +info_msg() { + echo -e "\n${Cya}$*${RCol}" +} + +error_msg() { + echo -e "\n${BRed}$*${RCol}" +} + +success_msg() { + echo -e "\n${BGre}$*${RCol}" +} + +travis_fold_start() { + echo "travis_fold:start:$*" +} + +travis_fold_end() { + echo "travis_fold:end:$*" +} diff --git a/README.md b/README.md index b2dc768a2c..32398eb243 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,16 @@ This is the complete source code and the build instructions for the alpha version of the official desktop client for the [Telegram][telegram] messenger, based on the [Telegram API][telegram_api] and the [MTProto][telegram_proto] secure protocol. +[![Build Status](https://travis-ci.org/telegramdesktop/tdesktop.svg?branch=master)](https://travis-ci.org/telegramdesktop/tdesktop) + The source code is published under GPLv3 with OpenSSL exception, the license is available [here][license]. ## Supported systems * Windows XP - Windows 10 (**not** RT) -* Mac OS X 10.8 - Mac OS X 10.10 +* Mac OS X 10.8 - Mac OS X 10.11 * Mac OS X 10.6 - Mac OS X 10.7 (separate build) -* Ubuntu 12.04 - Ubuntu 14.04 +* Ubuntu 12.04 - Ubuntu 15.04 * Fedora 22 ## Third-party libraries @@ -20,6 +22,8 @@ The source code is published under GPLv3 with OpenSSL exception, the license is * libexif 0.6.20 ([LGPL](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html)) * LZMA SDK 9.20 ([public domain](http://www.7-zip.org/sdk.html)) * liblzma ([public domain](http://tukaani.org/xz/)) +* Google Breakpad ([License](https://chromium.googlesource.com/breakpad/breakpad/+/master/LICENSE)) +* Google Crashpad ([Apache License 2.0](https://chromium.googlesource.com/crashpad/crashpad/+/master/LICENSE)) * OpenAL Soft ([LGPL](http://kcat.strangesoft.net/openal.html)) * Opus codec ([BSD license](http://www.opus-codec.org/license/)) * FFmpeg ([LGPL](https://www.ffmpeg.org/legal.html)) @@ -31,6 +35,7 @@ The source code is published under GPLv3 with OpenSSL exception, the license is * [XCode 7][xcode] * [XCode 7 for OS X 10.6 and 10.7][xcode_old] * [Qt Creator 3.5.1 Ubuntu][qtcreator] +* [Using qmake on GNU/Linux][qmake] ## Projects in Telegram solution @@ -46,21 +51,6 @@ The source code is published under GPLv3 with OpenSSL exception, the license is Compiles given files to single update file, compresses it with lzma and signs with a private key. It is not built in **Debug** and **Release** configurations of Telegram solution, because private key is inaccessible. -* ### Prepare - - Prepares a release for deployment, puts all current files to deploy/{version} folder. - - **Windows**: - * tsetup{version}.exe installer - * Telegram.exe - * Telegram.pdb (debug info for crash minidumps view) - * tupdate{updversion} binary lzma update archive - - **Mac**: - * tsetup{version}.dmg - * Telegram.app - * tmacupd{updversion} binary lzma update archive - * ### MetaEmoji Creates four sprites and text2emoji replace code @@ -92,7 +82,7 @@ The source code is published under GPLv3 with OpenSSL exception, the license is * ### MetaLang - Creates from languagepack file `Resources/lang.txt` language constants code and language file parse code: + Creates from languagepack file `Resources/lang.strings` language constants code and language file parse code: * GeneratedFiles/lang.h * GeneratedFiles/lang.cpp @@ -102,7 +92,8 @@ The source code is published under GPLv3 with OpenSSL exception, the license is [telegram_api]: https://core.telegram.org [telegram_proto]: https://core.telegram.org/mtproto [license]: LICENSE -[msvc]: MSVC.md -[xcode]: XCODE.md -[xcode_old]: XCODEold.md -[qtcreator]: QTCREATOR.md +[msvc]: doc/building-msvc.md +[xcode]: doc/building-xcode.md +[xcode_old]: doc/building-xcode-old.md +[qtcreator]: doc/building-qtcreator.md +[qmake]: doc/building-qmake.md diff --git a/Telegram.sln b/Telegram.sln index 836d4a043e..6487c468ac 100644 --- a/Telegram.sln +++ b/Telegram.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30501.0 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Telegram", "Telegram\Telegram.vcxproj", "{B12702AD-ABFB-343A-A199-8E24837244A3}" ProjectSection(ProjectDependencies) = postProject @@ -18,10 +18,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Updater", "Telegram\Updater EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MetaLang", "Telegram\MetaLang.vcxproj", "{E417CAA4-259B-4C99-88E3-805F1300E8EB}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2F863EAD-33C9-4014-A573-93F085BA9CB1}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "codegen", "codegen", "{2F863EAD-33C9-4014-A573-93F085BA9CB1}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Packer", "Telegram\Packer.vcxproj", "{56A9A4B2-21E5-4360-AFA8-85B43AC43B08}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "codegen_style", "Telegram\build\vc\codegen_style\codegen_style.vcxproj", "{E4DF8176-4DEF-4859-962F-B497E3E7A323}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -82,8 +84,21 @@ Global {56A9A4B2-21E5-4360-AFA8-85B43AC43B08}.Deploy|x64.ActiveCfg = Release|Win32 {56A9A4B2-21E5-4360-AFA8-85B43AC43B08}.Release|Win32.ActiveCfg = Release|Win32 {56A9A4B2-21E5-4360-AFA8-85B43AC43B08}.Release|x64.ActiveCfg = Release|Win32 + {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Debug|Win32.ActiveCfg = Debug|Win32 + {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Debug|Win32.Build.0 = Debug|Win32 + {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Debug|x64.ActiveCfg = Debug|Win32 + {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Deploy|Win32.ActiveCfg = Debug|Win32 + {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Deploy|Win32.Build.0 = Debug|Win32 + {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Deploy|x64.ActiveCfg = Release|Win32 + {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Deploy|x64.Build.0 = Release|Win32 + {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Release|Win32.ActiveCfg = Release|Win32 + {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Release|Win32.Build.0 = Release|Win32 + {E4DF8176-4DEF-4859-962F-B497E3E7A323}.Release|x64.ActiveCfg = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E4DF8176-4DEF-4859-962F-B497E3E7A323} = {2F863EAD-33C9-4014-A573-93F085BA9CB1} + EndGlobalSection EndGlobal diff --git a/Telegram/Build.bat b/Telegram/Build.bat index 650a937ee5..788f9c7668 100644 --- a/Telegram/Build.bat +++ b/Telegram/Build.bat @@ -1,4 +1,5 @@ @echo OFF +setlocal FOR /F "tokens=1,2* delims= " %%i in (Version) do set "%%i=%%j" @@ -29,6 +30,8 @@ set "HomePath=..\..\Telegram" set "ReleasePath=..\Win32\Deploy" set "DeployPath=%ReleasePath%\deploy\%AppVersionStrMajor%\%AppVersionStrFull%" set "SignPath=..\..\TelegramPrivate\Sign.bat" +set "BinaryName=Telegram" +set "DropboxSymbolsPath=Z:\Dropbox\Telegram\symbols" if %BetaVersion% neq 0 ( if exist %DeployPath%\ ( @@ -71,9 +74,13 @@ echo . echo Version %AppVersionStrFull% build successfull. Preparing.. echo . +echo Dumping debug symbols.. +call ..\..\Libraries\breakpad\src\tools\windows\binaries\dump_syms.exe %ReleasePath%\%BinaryName%.pdb > %ReleasePath%\%BinaryName%.sym +echo Done! + set "PATH=%PATH%;C:\Program Files\7-Zip;C:\Program Files (x86)\Inno Setup 5" -call %SignPath% %ReleasePath%\Telegram.exe +call %SignPath% %ReleasePath%\%BinaryName%.exe if %errorlevel% neq 0 goto error call %SignPath% %ReleasePath%\Updater.exe @@ -90,7 +97,7 @@ if %BetaVersion% equ 0 ( ) cd %ReleasePath% -call Packer.exe -version %VersionForPacker% -path Telegram.exe -path Updater.exe %DevParam% +call Packer.exe -version %VersionForPacker% -path %BinaryName%.exe -path Updater.exe %DevParam% cd %HomePath% if %errorlevel% neq 0 goto error @@ -109,6 +116,21 @@ if %BetaVersion% neq 0 ( set "PortableFile=tbeta%BetaVersion%_%BetaSignature%.zip" ) +for /f ^"usebackq^ eol^=^ + +^ delims^=^" %%a in (%ReleasePath%\%BinaryName%.sym) do ( + set "SymbolsHashLine=%%a" + goto symbolslinedone +) +:symbolslinedone +FOR /F "tokens=1,2,3,4* delims= " %%i in ("%SymbolsHashLine%") do set "SymbolsHash=%%l" + +echo Copying %BinaryName%.sym to %DropboxSymbolsPath%\%BinaryName%.pdb\%SymbolsHash% +if not exist %DropboxSymbolsPath%\%BinaryName%.pdb mkdir %DropboxSymbolsPath%\%BinaryName%.pdb +if not exist %DropboxSymbolsPath%\%BinaryName%.pdb\%SymbolsHash% mkdir %DropboxSymbolsPath%\%BinaryName%.pdb\%SymbolsHash% +xcopy %ReleasePath%\%BinaryName%.sym %DropboxSymbolsPath%\%BinaryName%.pdb\%SymbolsHash%\ +echo Done! + if not exist %ReleasePath%\deploy mkdir %ReleasePath%\deploy if not exist %ReleasePath%\deploy\%AppVersionStrMajor% mkdir %ReleasePath%\deploy\%AppVersionStrMajor% mkdir %DeployPath% @@ -144,7 +166,7 @@ if not exist %DeployPath%\%PortableFile% goto error if %BetaVersion% equ 0 ( if not exist %DeployPath%\%SetupFile% goto error ) -if not exist %DeployPath%\Telegram.pdb goto error +if not exist %DeployPath%\%BinaryName%.pdb goto error if not exist %DeployPath%\Updater.exe goto error if not exist %DeployPath%\Updater.pdb goto error if not exist %FinalReleasePath%\%AppVersionStrMajor% mkdir %FinalReleasePath%\%AppVersionStrMajor% @@ -157,7 +179,7 @@ if %BetaVersion% equ 0 ( ) else ( xcopy %DeployPath%\%BetaKeyFile% %FinalDeployPath%\ /Y ) -xcopy %DeployPath%\Telegram.pdb %FinalDeployPath%\ +xcopy %DeployPath%\%BinaryName%.pdb %FinalDeployPath%\ xcopy %DeployPath%\Updater.exe %FinalDeployPath%\ xcopy %DeployPath%\Updater.pdb %FinalDeployPath%\ diff --git a/Telegram/Build.sh b/Telegram/Build.sh index 3aa9b88f66..650e3692ba 100755 --- a/Telegram/Build.sh +++ b/Telegram/Build.sh @@ -1,5 +1,7 @@ set -e +FastParam="$1" + while IFS='' read -r line || [[ -n "$line" ]]; do set $line eval $1="$2" @@ -37,6 +39,7 @@ if [ "$BuildTarget" == "linux" ]; then WorkPath="./../Linux" FixScript="$HomePath/FixMake.sh" ReleasePath="./../Linux/Release" + BinaryName="Telegram" elif [ "$BuildTarget" == "linux32" ]; then echo "Building version $AppVersionStrFull for Linux 32bit.." UpdateFile="tlinux32upd$AppVersion" @@ -44,6 +47,7 @@ elif [ "$BuildTarget" == "linux32" ]; then WorkPath="./../Linux" FixScript="$HomePath/FixMake32.sh" ReleasePath="./../Linux/Release" + BinaryName="Telegram" elif [ "$BuildTarget" == "mac" ]; then echo "Building version $AppVersionStrFull for OS X 10.8+.." UpdateFile="tmacupd$AppVersion" @@ -104,22 +108,25 @@ fi #fi if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then + + DropboxSymbolsPath="/media/psf/Home/Dropbox/Telegram/symbols" + mkdir -p "$WorkPath/ReleaseIntermediateUpdater" cd "$WorkPath/ReleaseIntermediateUpdater" - /usr/local/Qt-5.5.1/bin/qmake "$HomePath/Updater.pro" + /usr/local/Qt-5.5.1/bin/qmake "$HomePath/Updater.pro" -r -spec linux-g++ make echo "Updater build complete!" cd "$HomePath" mkdir -p "$WorkPath/ReleaseIntermediate" cd "$WorkPath/ReleaseIntermediate" - /usr/local/Qt-5.5.1/bin/qmake "$HomePath/Telegram.pro" + /usr/local/Qt-5.5.1/bin/qmake "$HomePath/Telegram.pro" -r -spec linux-g++ eval "$FixScript" make - echo "Telegram build complete!" + echo "$BinaryName build complete!" cd "$HomePath" - if [ ! -f "$ReleasePath/Telegram" ]; then - echo "Telegram not found!" + if [ ! -f "$ReleasePath/$BinaryName" ]; then + echo "$BinaryName not found!" exit 1 fi @@ -128,8 +135,16 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then exit 1 fi + echo "Dumping debug symbols.." + "./../../Libraries/breakpad/src/tools/linux/dump_syms/dump_syms" "$ReleasePath/$BinaryName" > "$ReleasePath/$BinaryName.sym" + echo "Done!" + + echo "Stripping the executable.." + strip -s "$ReleasePath/$BinaryName" + echo "Done!" + echo "Preparing version $AppVersionStrFull, executing Packer.." - cd "$ReleasePath" && "./Packer" -path Telegram -path Updater -version $VersionForPacker $DevParam && cd "$HomePath" + cd "$ReleasePath" && "./Packer" -path "$BinaryName" -path Updater -version $VersionForPacker $DevParam && cd "$HomePath" echo "Packer done!" if [ "$BetaVersion" != "0" ]; then @@ -146,6 +161,12 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then SetupFile="tbeta${BetaVersion}_${BetaSignature}.tar.xz" fi + SymbolsHash=`head -n 1 "$ReleasePath/$BinaryName.sym" | awk -F " " 'END {print $4}'` + echo "Copying $BinaryName.sym to $DropboxSymbolsPath/$BinaryName/$SymbolsHash" + mkdir -p "$DropboxSymbolsPath/$BinaryName/$SymbolsHash" + cp "$ReleasePath/$BinaryName.sym" "$DropboxSymbolsPath/$BinaryName/$SymbolsHash/" + echo "Done!" + if [ ! -d "$ReleasePath/deploy" ]; then mkdir "$ReleasePath/deploy" fi @@ -154,21 +175,25 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then mkdir "$ReleasePath/deploy/$AppVersionStrMajor" fi - echo "Copying Telegram, Updater and $UpdateFile to deploy/$AppVersionStrMajor/$AppVersionStrFull.."; + echo "Copying $BinaryName, Updater and $UpdateFile to deploy/$AppVersionStrMajor/$AppVersionStrFull.."; mkdir "$DeployPath" - mkdir "$DeployPath/Telegram" - mv "$ReleasePath/Telegram" "$DeployPath/Telegram/" - mv "$ReleasePath/Updater" "$DeployPath/Telegram/" + mkdir "$DeployPath/$BinaryName" + mv "$ReleasePath/$BinaryName" "$DeployPath/$BinaryName/" + mv "$ReleasePath/Updater" "$DeployPath/$BinaryName/" mv "$ReleasePath/$UpdateFile" "$DeployPath/" if [ "$BetaVersion" != "0" ]; then mv "$ReleasePath/$BetaKeyFile" "$DeployPath/" fi - cd "$DeployPath" && tar -cJvf "$SetupFile" "Telegram/" && cd "./../../../$HomePath" + cd "$DeployPath" && tar -cJvf "$SetupFile" "$BinaryName/" && cd "./../../../$HomePath" fi if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarget" == "macstore" ]; then - touch "./SourceFiles/telegram.qrc" + DropboxSymbolsPath="./../../../Dropbox/Telegram/symbols" + + if [ "$FastParam" != "fast" ]; then + touch "./SourceFiles/telegram.qrc" + fi xcodebuild -project Telegram.xcodeproj -alltargets -configuration Release build if [ ! -d "$ReleasePath/$BinaryName.app" ]; then @@ -181,6 +206,28 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarg exit 1 fi + if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then + echo "Removing Updater debug symbols.." + rm -rf "$ReleasePath/$BinaryName.app/Contents/Frameworks/Updater.dSYM" + echo "Done!" + fi + + echo "Dumping debug symbols.." + "./../../Libraries/breakpad/src/tools/mac/dump_syms/build/Release/dump_syms" "$ReleasePath/$BinaryName.app.dSYM" > "$ReleasePath/$BinaryName.sym" 2>/dev/null + echo "Done!" + + echo "Stripping the executable.." + strip "$ReleasePath/$BinaryName.app/Contents/MacOS/$BinaryName" + echo "Done!" + + echo "Signing the application.." + if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then + codesign --force --deep --sign "Developer ID Application: John Preston" "$ReleasePath/$BinaryName.app" + elif [ "$BuildTarget" == "macstore" ]; then + codesign --force --deep --sign "3rd Party Mac Developer Application: TELEGRAM MESSENGER LLP (6N38VWS5BX)" "$ReleasePath/$BinaryName.app" --entitlements "Telegram/Telegram Desktop.entitlements" + fi + echo "Done!" + AppUUID=`dwarfdump -u "$ReleasePath/$BinaryName.app/Contents/MacOS/$BinaryName" | awk -F " " '{print $2}'` DsymUUID=`dwarfdump -u "$ReleasePath/$BinaryName.app.dSYM" | awk -F " " '{print $2}'` if [ "$AppUUID" != "$DsymUUID" ]; then @@ -215,6 +262,12 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarg fi fi + SymbolsHash=`head -n 1 "$ReleasePath/$BinaryName.sym" | awk -F " " 'END {print $4}'` + echo "Copying $BinaryName.sym to $DropboxSymbolsPath/$BinaryName/$SymbolsHash" + mkdir -p "$DropboxSymbolsPath/$BinaryName/$SymbolsHash" + cp "$ReleasePath/$BinaryName.sym" "$DropboxSymbolsPath/$BinaryName/$SymbolsHash/" + echo "Done!" + if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then if [ "$BetaVersion" == "0" ]; then cd "$ReleasePath" @@ -254,10 +307,10 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarg if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ]; then echo "Copying $BinaryName.app and $UpdateFile to deploy/$AppVersionStrMajor/$AppVersionStr.."; mkdir "$DeployPath" - mkdir "$DeployPath/Telegram" - cp -r "$ReleasePath/$BinaryName.app" "$DeployPath/Telegram/" + mkdir "$DeployPath/$BinaryName" + cp -r "$ReleasePath/$BinaryName.app" "$DeployPath/$BinaryName/" if [ "$BetaVersion" != "0" ]; then - cd "$DeployPath" && zip -r "$SetupFile" "Telegram" && mv "$SetupFile" "./../../../" && cd "./../../../$HomePath" + cd "$DeployPath" && zip -r "$SetupFile" "$BinaryName" && mv "$SetupFile" "./../../../" && cd "./../../../$HomePath" mv "$ReleasePath/$BetaKeyFile" "$DeployPath/" fi mv "$ReleasePath/$BinaryName.app.dSYM" "$DeployPath/" @@ -294,6 +347,7 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarg rm "$ReleasePath/$BinaryName.app/Contents/MacOS/$BinaryName" rm -rf "$ReleasePath/$BinaryName.app/Contents/_CodeSignature" + mkdir -p "$DropboxDeployPath" cp -v "$DeployPath/$BinaryName.app" "$DropboxDeployPath/" cp -rv "$DeployPath/$BinaryName.app.dSYM" "$DropboxDeployPath/" fi diff --git a/Telegram/Deploy.sh b/Telegram/Deploy.sh index 058cbf09d2..3f40b0fc75 100755 --- a/Telegram/Deploy.sh +++ b/Telegram/Deploy.sh @@ -57,7 +57,11 @@ elif [ "$BuildTarget" == "mac" ]; then echo "Deploying version $AppVersionStrFull for Windows.." else DeployMac="1" - DeployMac32="1" + if [ "$BetaVersion" != "0" ]; then + DeployMac32="0" + else + DeployMac32="1" + fi DeployWin="1" echo "Deploying three versions of $AppVersionStrFull: for Windows, OS X 10.6 and 10.7 and OS X 10.8+.." fi @@ -165,13 +169,7 @@ fi fi fi - if [ ! -d "$DropboxPath" ]; then - mkdir "$DropboxPath" - fi - - if [ ! -d "$DropboxDeployPath" ]; then - mkdir "$DropboxDeployPath" - fi + mkdir -p "$DropboxDeployPath" fi #fi @@ -194,7 +192,6 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ] || [ "$Build fi if [ "$DeployMac" == "1" ]; then - cp -v "$DeployPath/$UpdateFile" "$DropboxDeployPath/" cp -v "$DeployPath/$SetupFile" "$DropboxDeployPath/$DropboxSetupFile" if [ -d "$DropboxDeployPath/Telegram.app.dSYM" ]; then rm -rf "$DropboxDeployPath/Telegram.app.dSYM" @@ -202,7 +199,6 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ] || [ "$Build cp -rv "$DeployPath/Telegram.app.dSYM" "$DropboxDeployPath/" fi if [ "$DeployMac32" == "1" ]; then - mv -v "$Mac32DeployPath/$Mac32UpdateFile" "$DropboxDeployPath/" mv -v "$Mac32DeployPath/$Mac32SetupFile" "$DropboxDeployPath/$DropboxMac32SetupFile" if [ -d "$DropboxDeployPath/Telegram32.app.dSYM" ]; then rm -rf "$DropboxDeployPath/Telegram32.app.dSYM" @@ -213,7 +209,6 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ] || [ "$Build mv -v "$WinDeployPath/Telegram.pdb" "$DropboxDeployPath/" mv -v "$WinDeployPath/Updater.exe" "$DropboxDeployPath/" mv -v "$WinDeployPath/Updater.pdb" "$DropboxDeployPath/" - mv -v "$WinDeployPath/$WinUpdateFile" "$DropboxDeployPath/" if [ "$BetaVersion" == "0" ]; then mv -v "$WinDeployPath/$WinSetupFile" "$DropboxDeployPath/" fi diff --git a/Telegram/FixMake.sh b/Telegram/FixMake.sh index 21abada2b2..74b2c6b036 100755 --- a/Telegram/FixMake.sh +++ b/Telegram/FixMake.sh @@ -11,12 +11,7 @@ Replace () { } Replace '\-llzma' '\/usr\/lib\/x86_64\-linux\-gnu\/liblzma\.a' -Replace '\-lz' '\/usr\/lib\/x86_64\-linux\-gnu\/libz\.a' -Replace '\-lssl' '\/usr\/lib\/x86_64\-linux\-gnu\/libssl\.a' -Replace '\-lcrypto' '\/usr\/lib\/x86_64\-linux\-gnu\/libcrypto\.a' -Replace '\-lexif' '\/usr\/lib\/x86_64\-linux\-gnu\/libexif\.a' -Replace '\-lgobject\-2\.0' '\/usr\/lib\/x86_64\-linux\-gnu\/libgobject\-2\.0\.a \/usr\/lib\/x86_64\-linux\-gnu\/libffi\.a' -Replace '\-lXi' '\/usr\/lib\/x86_64\-linux\-gnu\/libXi\.a' +Replace '\-lXi' '\/usr\/lib\/x86_64\-linux\-gnu\/libXi\.a \/usr\/lib\/x86_64\-linux\-gnu\/libXext\.a' Replace '\-lSM' '\/usr\/lib\/x86_64\-linux\-gnu\/libSM\.a' Replace '\-lICE' '\/usr\/lib\/x86_64\-linux\-gnu\/libICE\.a' Replace '\-lfontconfig' '\/usr\/lib\/x86_64\-linux\-gnu\/libfontconfig\.a \/usr\/lib\/x86_64\-linux\-gnu\/libexpat\.a' @@ -30,3 +25,4 @@ Replace '\-lswresample' '\/usr\/local\/lib\/libswresample\.a' Replace '\-lswscale' '\/usr\/local\/lib\/libswscale\.a' Replace '\-lavutil' '\/usr\/local\/lib\/libavutil\.a' Replace '\-lva' '\/usr\/local\/lib\/libva\.a' +Replace '\-lQt5Network' '\/usr\/local\/Qt-5.5.1\/lib\/libQt5Network.a \/usr\/local\/ssl\/lib\/libssl.a \/usr\/local\/ssl\/lib\/libcrypto.a' diff --git a/Telegram/FixMake32.sh b/Telegram/FixMake32.sh index 833bca6c6d..24d5e677f9 100755 --- a/Telegram/FixMake32.sh +++ b/Telegram/FixMake32.sh @@ -11,12 +11,7 @@ Replace () { } Replace '\-llzma' '\/usr\/lib\/i386\-linux\-gnu\/liblzma\.a' -Replace '\-lz' '\/usr\/lib\/i386\-linux\-gnu\/libz\.a' -Replace '\-lssl' '\/usr\/lib\/i386\-linux\-gnu\/libssl\.a' -Replace '\-lcrypto' '\/usr\/lib\/i386\-linux\-gnu\/libcrypto\.a' -Replace '\-lexif' '\/usr\/lib\/i386\-linux\-gnu\/libexif\.a' -Replace '\-lgobject\-2\.0' '\/usr\/lib\/i386\-linux\-gnu\/libgobject\-2\.0\.a \/usr\/lib\/i386\-linux\-gnu\/libffi\.a' -Replace '\-lXi' '\/usr\/lib\/i386\-linux\-gnu\/libXi\.a' +Replace '\-lXi' '\/usr\/lib\/i386\-linux\-gnu\/libXi\.a \/usr\/lib\/i386\-linux\-gnu\/libXext\.a' Replace '\-lSM' '\/usr\/lib\/i386\-linux\-gnu\/libSM\.a' Replace '\-lICE' '\/usr\/lib\/i386\-linux\-gnu\/libICE\.a' Replace '\-lfontconfig' '\/usr\/lib\/i386\-linux\-gnu\/libfontconfig\.a \/usr\/lib\/i386\-linux\-gnu\/libexpat\.a' @@ -30,3 +25,4 @@ Replace '\-lswresample' '\/usr\/local\/lib\/libswresample\.a' Replace '\-lswscale' '\/usr\/local\/lib\/libswscale\.a' Replace '\-lavutil' '\/usr\/local\/lib\/libavutil\.a' Replace '\-lva' '\/usr\/local\/lib\/libva\.a' +Replace '\-lQt5Network' '\/usr\/local\/Qt-5.5.1\/lib\/libQt5Network.a \/usr\/local\/ssl\/lib\/libssl.a \/usr\/local\/ssl\/lib\/libcrypto.a' diff --git a/Telegram/MetaLang.pro b/Telegram/MetaLang.pro index dbef70ae94..0dfa29a6fd 100644 --- a/Telegram/MetaLang.pro +++ b/Telegram/MetaLang.pro @@ -12,7 +12,7 @@ CONFIG(release, debug|release) { DESTDIR = ./../ReleaseLang } -CONFIG += plugin static +CONFIG += plugin static c++11 macx { QMAKE_INFO_PLIST = ./SourceFiles/_other/Lang.plist diff --git a/Telegram/MetaStyle.pro b/Telegram/MetaStyle.pro index 2ef67ea2e0..a68a443856 100644 --- a/Telegram/MetaStyle.pro +++ b/Telegram/MetaStyle.pro @@ -12,7 +12,7 @@ CONFIG(release, debug|release) { DESTDIR = ./../ReleaseStyle } -CONFIG += plugin static +CONFIG += plugin static c++11 macx { QMAKE_INFO_PLIST = ./SourceFiles/_other/Style.plist diff --git a/Telegram/Resources/LangList b/Telegram/Resources/LangList new file mode 100644 index 0000000000..6581227605 --- /dev/null +++ b/Telegram/Resources/LangList @@ -0,0 +1 @@ +de,es,it,ko,nl,pt_BR diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index 7d8eddad43..3e5a90d59b 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ "lng_language_name" = "English"; "lng_switch_to_this" = "Switch to English"; @@ -86,8 +86,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_cancel" = "Cancel"; "lng_continue" = "Continue"; "lng_close" = "Close"; -"lng_connecting" = "Connecting.."; -"lng_reconnecting" = "Reconnect {count:now|in # s|in # s}.."; +"lng_connecting" = "Connecting..."; +"lng_reconnecting" = "Reconnect {count:now|in # s|in # s}..."; "lng_reconnecting_try_now" = "Try now"; "lng_status_service_notifications" = "service notifications"; @@ -108,7 +108,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_status_lastseen_date" = "last seen {date}"; "lng_status_lastseen_date_time" = "last seen {date} at {time}"; "lng_status_online" = "online"; -"lng_status_connecting" = "connecting.."; +"lng_status_connecting" = "connecting..."; "lng_chat_status_unaccessible" = "group is unaccessible"; "lng_chat_status_members" = "{count:no members|# member|# members}"; @@ -123,8 +123,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_server_error" = "Internal server error."; "lng_flood_error" = "Too many tries. Please try again later."; "lng_gif_error" = "An error has occured while reading GIF animation :("; +"lng_edit_error" = "You cannot edit this message"; +"lng_edit_deleted" = "This message was deleted"; +"lng_edit_too_long" = "Your message text is too long"; +"lng_edit_message" = "Edit message"; +"lng_edit_message_text" = "New message text..."; "lng_deleted" = "Unknown"; "lng_deleted_message" = "Deleted message"; +"lng_pinned_message" = "Pinned message"; +"lng_pinned_unpin_sure" = "Would you like to unpin this message?"; +"lng_pinned_pin_sure" = "Would you like to pin this message?"; +"lng_pinned_pin" = "Pin"; +"lng_pinned_unpin" = "Unpin"; +"lng_pinned_notify" = "Notify all members"; "lng_intro" = "Welcome to the official [a href=\"https://telegram.org/\"]Telegram[/a] desktop app.\nIt's [b]fast[/b] and [b]secure[/b]."; "lng_start_msgs" = "START MESSAGING"; @@ -151,7 +162,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_code_telegram" = "Please enter the code you've just\nreceived in your previous [b]Telegram[/b] app."; "lng_code_no_telegram" = "Send code via SMS"; "lng_code_call" = "Telegram will dial your number in {minutes}:{seconds}"; -"lng_code_calling" = "Requesting a call from Telegram.."; +"lng_code_calling" = "Requesting a call from Telegram..."; "lng_code_called" = "Telegram dialed your number"; "lng_bad_phone" = "Invalid phone number. Please try again."; @@ -190,7 +201,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_dlg_new_channel_name" = "Channel name"; "lng_no_contacts" = "You have no contacts"; "lng_no_chats" = "Your chats will be here"; -"lng_contacts_loading" = "Loading.."; +"lng_contacts_loading" = "Loading..."; "lng_contacts_not_found" = "No contacts found"; "lng_dlg_search_chat" = "Search in this chat"; "lng_dlg_search_channel" = "Search in this channel"; @@ -199,7 +210,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_settings_save" = "Save"; "lng_settings_upload" = "Set Profile Photo"; "lng_settings_crop_profile" = "Select a square area for your profile photo"; -"lng_settings_uploading_photo" = "Uploading photo.."; +"lng_settings_uploading_photo" = "Uploading photo..."; "lng_username_title" = "Username"; "lng_username_about" = "You can choose a username on Telegram.\nIf you do, other people will be able to find\nyou by this username and contact you\nwithout knowing your phone number.\n\nYou can use a-z, 0-9 and underscores.\nMinimum length is 5 characters."; @@ -236,9 +247,9 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_settings_auto_update" = "Update automatically"; "lng_settings_current_version" = "Version {version}"; "lng_settings_check_now" = "Check for updates"; -"lng_settings_update_checking" = "Checking for updates.."; +"lng_settings_update_checking" = "Checking for updates..."; "lng_settings_latest_installed" = "Latest version is installed"; -"lng_settings_downloading" = "Downloading update {ready} / {total} MB.."; +"lng_settings_downloading" = "Downloading update {ready} / {total} MB..."; "lng_settings_update_ready" = "New version is ready"; "lng_settings_update_now" = "Restart Now"; "lng_settings_update_fail" = "Update check failed :("; @@ -262,6 +273,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_settings_bg_from_gallery" = "Choose from gallery"; "lng_settings_bg_from_file" = "Choose from file"; "lng_settings_bg_tile" = "Tile background"; +"lng_settings_adaptive_wide" = "Adaptive layout for wide screens"; "lng_backgrounds_header" = "Choose your new chat background"; @@ -279,7 +291,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_download_path_failed" = "File download could not be started. It could happen because of a bad download location.\n\nYou can change download path in Settings."; "lng_download_path_settings" = "Settings"; "lng_download_finish_failed" = "File download could not be finished.\n\nWould you like to try again?"; -"lng_download_path_clearing" = "Clearing.."; +"lng_download_path_clearing" = "Clearing..."; "lng_download_path_cleared" = "Cleared!"; "lng_download_path_clear_failed" = "Clear failed :("; @@ -288,7 +300,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_settings_images_cached" = "{count:_not_used_|# image|# images}, {size}"; "lng_settings_audios_cached" = "{count:_not_used_|# voice message|# voice messages}, {size}"; "lng_local_storage_clear" = "Clear all"; -"lng_local_storage_clearing" = "Clearing.."; +"lng_local_storage_clearing" = "Clearing..."; "lng_local_storage_cleared" = "Cleared!"; "lng_local_storage_clear_failed" = "Clear failed :("; @@ -319,7 +331,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_passcode_logout" = "Log out"; "lng_passcode_need_unblock" = "You need to unlock me first."; -"lng_cloud_password_waiting" = "Confirmation link sent to {email}.."; +"lng_cloud_password_waiting" = "Confirmation link sent to {email}..."; "lng_cloud_password_change" = "Change cloud password"; "lng_cloud_password_create" = "Cloud password"; "lng_cloud_password_remove" = "Remove cloud password"; @@ -346,9 +358,9 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_cloud_password_is_same" = "Password was not changed"; "lng_connection_type" = "Connection type:"; -"lng_connection_auto_connecting" = "Default (connecting..)"; +"lng_connection_auto_connecting" = "Default (connecting...)"; "lng_connection_auto" = "Default ({transport} used)"; -"lng_connection_proxy_connecting" = "Connecting through proxy.."; +"lng_connection_proxy_connecting" = "Connecting through proxy..."; "lng_connection_proxy" = "{transport} with proxy"; "lng_connection_header" = "Connection type"; "lng_connection_auto_rb" = "Auto (TCP if available or HTTP)"; @@ -384,7 +396,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_sessions_other_desc" = "You can log in to Telegram from other mobile, tablet and desktop devices, using the same phone number. All your data will be instantly synchronized."; "lng_sessions_terminate_all" = "Terminate all other sessions"; -"lng_preview_loading" = "Getting Link Info.."; +"lng_preview_loading" = "Getting Link Info..."; "lng_profile_chat_unaccessible" = "Group is unaccessible"; "lng_topbar_info" = "Info"; @@ -423,10 +435,11 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_profile_add_participant" = "Add Members"; "lng_profile_delete_and_exit" = "Leave"; "lng_profile_kick" = "Remove"; +"lng_profile_admin" = "admin"; "lng_profile_sure_kick" = "Remove {user} from the group?"; "lng_profile_sure_kick_channel" = "Remove {user} from the channel?"; "lng_profile_sure_kick_admin" = "Remove {user} from administrators?"; -"lng_profile_loading" = "Loading.."; +"lng_profile_loading" = "Loading..."; "lng_profile_shared_media" = "Shared media"; "lng_profile_no_media" = "No media in this conversation."; "lng_profile_photos" = "{count:_not_used_|# photo|# photos} »"; @@ -442,6 +455,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_profile_shared_links" = "{count:_not_used_|# shared link|# shared links} »"; "lng_profile_shared_links_header" = "Shared links overview"; "lng_profile_copy_phone" = "Copy phone number"; +"lng_profile_copy_fullname" = "Copy name"; "lng_channel_add_admins" = "New administrator"; "lng_channel_add_members" = "Add members"; @@ -463,13 +477,17 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_create_group_next" = "Next"; "lng_create_group_create" = "Create"; "lng_create_group_title" = "New Group"; -"lng_create_group_about" = "Groups are ideal for smaller communities,\nthey can have up to {count:_not_used|# member|# members}"; +"lng_create_group_about" = "Groups are ideal for limited communities,\nthey can have up to {count:_not_used|# member|# members}"; "lng_create_channel_title" = "New Channel"; "lng_create_channel_about" = "Channels are a tool for broadcasting your messages to unlimited audiences"; "lng_create_public_channel_title" = "Public Channel"; "lng_create_public_channel_about" = "Anyone can find the channel in search and join"; "lng_create_private_channel_title" = "Private Channel"; "lng_create_private_channel_about" = "Only people with a special invite link may join"; +"lng_create_public_group_title" = "Public Group"; +"lng_create_public_group_about" = "Anyone can find the group in search and join, chat history is available to everybody"; +"lng_create_private_group_title" = "Private Group"; +"lng_create_private_group_about" = "People can only join if they were invited or have an invite link"; "lng_create_channel_comments" = "Enable Comments"; "lng_create_channel_comments_about" = "If you enable comments, members will be able to discuss your posts in the channel"; "lng_create_group_skip" = "Skip"; @@ -498,7 +516,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_sure_delete_group" = "Are you sure, you want to delete this group? All members will be removed and all messages will be lost."; "lng_message_empty" = "Empty Message"; -"lng_media_unsupported" = "Media Unsupported"; +"lng_message_unsupported" = "This message is not supported by your version of Telegram Desktop. Please update to the last version in Settings or install it from {link}"; "lng_action_add_user" = "{from} added {user}"; "lng_action_add_users_many" = "{from} added {users}"; @@ -522,6 +540,17 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_action_created_chat" = "{from} created group «{title}»"; "lng_action_created_channel" = "Channel «{title}» created"; "lng_action_group_migrate" = "The group was upgraded to a supergroup"; +"lng_action_pinned_message" = "{from} pinned «{text}»"; +"lng_action_pinned_media" = "{from} pinned {media}"; +"lng_action_pinned_media_photo" = "a photo"; +"lng_action_pinned_media_video" = "a video file"; +"lng_action_pinned_media_audio" = "an audio file"; +"lng_action_pinned_media_voice" = "a voice message"; +"lng_action_pinned_media_file" = "a file"; +"lng_action_pinned_media_gif" = "a GIF animation"; +"lng_action_pinned_media_contact" = "a contact information"; +"lng_action_pinned_media_location" = "a location mark"; +"lng_action_pinned_media_sticker" = "a sticker"; "lng_profile_migrate_reached" = "{count:_not_used_|# member|# members} limit reached"; "lng_profile_migrate_about" = "If you'd like to go over this limit, you can upgrade your group to a supergroup. In supergroups:"; @@ -531,6 +560,16 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_profile_migrate_feature4" = "— Notifications are muted by default"; "lng_profile_migrate_button" = "Upgrade to supergroup"; "lng_profile_migrate_sure" = "Are you sure you want to upgrade this group to supergroup? This action cannot be undone."; +"lng_profile_convert_button" = "Convert to supergroup"; +"lng_profile_convert_title" = "Convert to supergroup"; +"lng_profile_convert_about" = "In supergroups:"; +"lng_profile_convert_feature1" = "— New members see the full message history"; +"lng_profile_convert_feature2" = "— Messages are deleted for all members"; +"lng_profile_convert_feature3" = "— Members can edit their own messages"; +"lng_profile_convert_feature4" = "— Creator can set a public link for the group"; +"lng_profile_convert_warning" = "{bold_start}Note:{bold_end} This action can not be undone"; +"lng_profile_convert_confirm" = "Convert"; +"lng_profile_add_more_after_upgrade" = "You will be able to add up to {count:_not_used_|# member|# members} after you upgrade your group to a supergroup."; "lng_channel_comments_count" = "{count:_not_used_|# comment|# comments}"; "lng_channel_hide_comments" = "Hide comments"; @@ -555,9 +594,18 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_channel_public_link_copied" = "Link copied to clipboard."; -"lng_forwarded_from" = "Forwarded from"; +"lng_forwarded" = "Forwarded from {user}"; +"lng_forwarded_channel" = "Forwarded from {channel}"; +"lng_forwarded_via" = "Forwarded from {user} via {inline_bot}"; +"lng_forwarded_channel_via" = "Forwarded from {channel} via {inline_bot}"; +"lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "In reply to"; +"lng_bot_share_location_unavailable" = "Sorry, the location sharing is currently unavailable in Telegram Desktop."; +"lng_bot_inline_geo_unavailable" = "Sorry, this bot requires location sharing.\nIt is not unavailable in Telegram Desktop."; +"lng_bot_share_phone" = "Share Phone Number?"; +"lng_bot_share_phone_confirm" = "Share"; + "lng_attach_failed" = "Failed"; "lng_attach_file" = "File"; "lng_attach_photo" = "Photo"; @@ -600,6 +648,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_saved_gifs" = "Saved GIFs"; "lng_inline_bot_results" = "Results from {inline_bot}"; "lng_inline_bot_no_results" = "No results"; +"lng_inline_bot_via" = "via {inline_bot}"; "lng_box_remove" = "Remove"; @@ -616,16 +665,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_stickers_remove" = "Delete"; "lng_stickers_return" = "Undo"; "lng_stickers_restore" = "Restore"; -"lng_stickers_count" = "{count:Loading..|# sticker|# stickers}"; +"lng_stickers_count" = "{count:Loading...|# sticker|# stickers}"; "lng_in_dlg_photo" = "Photo"; -"lng_in_dlg_video" = "Video"; +"lng_in_dlg_video" = "Video file"; +"lng_in_dlg_audio_file" = "Audio file"; "lng_in_dlg_contact" = "Contact"; -"lng_in_dlg_audio" = "Audio"; +"lng_in_dlg_audio" = "Voice message"; "lng_in_dlg_file" = "File"; "lng_in_dlg_sticker" = "Sticker"; "lng_in_dlg_sticker_emoji" = "{emoji} (sticker)"; +"lng_ban_user" = "Ban User"; +"lng_delete_all_from" = "Delete all from this user"; "lng_report_spam" = "Report Spam"; "lng_report_spam_hide" = "Hide"; "lng_report_spam_thanks" = "Thank you for your report!"; @@ -633,16 +685,23 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_report_spam_sure_group" = "Are you sure you want to report spam in this group?"; "lng_report_spam_sure_channel" = "Are you sure you want to report spam in this channel?"; "lng_report_spam_ok" = "Report"; -"lng_cant_send_to_not_contact" = "Sorry, you can only send messages to\nmutual contacts at the moment. {more_info}"; -"lng_cant_invite_not_contact" = "Sorry, you can only add mutual contacts\nto groups at the moment. {more_info}"; -"lng_cant_invite_not_contact_channel" = "Sorry, you can only add mutual contacts\nto channels at the moment. {more_info}"; +"lng_cant_send_to_not_contact" = "Sorry, you can only send messages to\nmutual contacts at the moment.\n{more_info}"; +"lng_cant_invite_not_contact" = "Sorry, you can only add mutual contacts\nto groups at the moment.\n{more_info}"; +"lng_cant_invite_not_contact_channel" = "Sorry, you can only add mutual contacts\nto channels at the moment.\n{more_info}"; "lng_cant_more_info" = "More info »"; +"lng_cant_invite_banned" = "Sorry, only admin can add this user."; +"lng_cant_invite_privacy" = "Sorry, you cannot add this user to groups because of the privacy settings."; +"lng_cant_invite_privacy_channel" = "Sorry, you cannot add this user to channels because of the privacy settings."; +"lng_cant_do_this" = "Sorry, this action is unavailable."; "lng_send_button" = "Send"; -"lng_message_ph" = "Write a message.."; -"lng_comment_ph" = "Write a comment.."; -"lng_broadcast_ph" = "Broadcast a message.."; +"lng_message_ph" = "Write a message..."; +"lng_comment_ph" = "Write a comment..."; +"lng_broadcast_ph" = "Broadcast a message..."; +"lng_broadcast_silent_ph" = "Silent broadcast..."; "lng_record_cancel" = "Release outside this field to cancel"; +"lng_will_be_notified" = "Members will be notified when you post"; +"lng_wont_be_notified" = "Members will not be notified when you post"; "lng_empty_history" = ""; "lng_willbe_history" = "Please select a chat to start messaging"; "lng_message_with_from" = "[c]{from}:[/c] {message}"; @@ -667,28 +726,29 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_user_typing" = "{user} is typing"; "lng_users_typing" = "{user} and {second_user} are typing"; "lng_many_typing" = "{count:_not_used_|# is|# are} typing"; -"lng_send_action_record_video" = "recording video"; -"lng_user_action_record_video" = "{user} is recording video"; -"lng_send_action_upload_video" = "sending video"; -"lng_user_action_upload_video" = "{user} is sending video"; -"lng_send_action_record_audio" = "recording audio"; -"lng_user_action_record_audio" = "{user} is recording audio"; -"lng_send_action_upload_audio" = "sending audio"; -"lng_user_action_upload_audio" = "{user} is sending audio"; -"lng_send_action_upload_photo" = "sending photo"; -"lng_user_action_upload_photo" = "{user} is sending photo"; -"lng_send_action_upload_file" = "sending file"; -"lng_user_action_upload_file" = "{user} is sending file"; -"lng_send_action_geo_location" = "picking location"; -"lng_user_action_geo_location" = "{user} is picking location"; -"lng_send_action_choose_contact" = "choosing contact"; -"lng_user_action_choose_contact" = "{user} is choosing contact"; +"lng_send_action_record_video" = "recording a video"; +"lng_user_action_record_video" = "{user} is recording a video"; +"lng_send_action_upload_video" = "sending a video"; +"lng_user_action_upload_video" = "{user} is sending a video"; +"lng_send_action_record_audio" = "recording a voice message"; +"lng_user_action_record_audio" = "{user} is recording a voice message"; +"lng_send_action_upload_audio" = "sending a voice message"; +"lng_user_action_upload_audio" = "{user} is sending a voice message"; +"lng_send_action_upload_photo" = "sending a photo"; +"lng_user_action_upload_photo" = "{user} is sending a photo"; +"lng_send_action_upload_file" = "sending a file"; +"lng_user_action_upload_file" = "{user} is sending a file"; +"lng_send_action_geo_location" = "picking a location"; +"lng_user_action_geo_location" = "{user} is picking a location"; +"lng_send_action_choose_contact" = "choosing a contact"; +"lng_user_action_choose_contact" = "{user} is choosing a contact"; "lng_unread_bar" = "{count:_not_used_|# unread message|# unread messages}"; "lng_maps_point" = "Location"; "lng_save_photo" = "Save image"; -"lng_save_video" = "Save video"; -"lng_save_audio" = "Save audio"; +"lng_save_video" = "Save video file"; +"lng_save_audio_file" = "Save audio file"; +"lng_save_audio" = "Save voice message"; "lng_save_file" = "Save file"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; @@ -700,16 +760,12 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_context_view_group" = "View group info"; "lng_context_view_channel" = "View channel info"; -"lng_context_open_link" = "Open Link"; "lng_context_copy_link" = "Copy Link"; -"lng_context_open_email" = "Write to this address"; +"lng_context_copy_post_link" = "Copy Post Link"; "lng_context_copy_email" = "Copy email address"; -"lng_context_open_hashtag" = "Search by hashtag"; "lng_context_copy_hashtag" = "Copy hashtag"; -"lng_context_open_mention" = "Open profile"; "lng_context_copy_mention" = "Copy username"; -"lng_context_open_image" = "Open Image"; -"lng_context_save_image" = "Save Image As.."; +"lng_context_save_image" = "Save Image As..."; "lng_context_forward_image" = "Forward Image"; "lng_context_delete_image" = "Delete Image"; "lng_context_copy_image" = "Copy Image"; @@ -717,14 +773,12 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_context_cancel_download" = "Cancel Download"; "lng_context_show_in_folder" = "Show in Folder"; "lng_context_show_in_finder" = "Show in Finder"; -"lng_context_open_video" = "Open Video"; -"lng_context_save_video" = "Save Video As.."; -"lng_context_open_audio" = "Open Audio"; -"lng_context_save_audio" = "Save Audio As.."; +"lng_context_save_video" = "Save Video File As..."; +"lng_context_save_audio_file" = "Save Audio File As..."; +"lng_context_save_audio" = "Save Voice Message As..."; "lng_context_pack_info" = "Pack Info"; "lng_context_pack_add" = "Add Stickers"; -"lng_context_open_file" = "Open File"; -"lng_context_save_file" = "Save File As.."; +"lng_context_save_file" = "Save File As..."; "lng_context_forward_file" = "Forward File"; "lng_context_delete_file" = "Delete File"; "lng_context_close_file" = "Close File"; @@ -732,9 +786,12 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_context_save_gif" = "Save GIF"; "lng_context_to_msg" = "Go To Message"; "lng_context_reply_msg" = "Reply"; +"lng_context_edit_msg" = "Edit"; "lng_context_forward_msg" = "Forward Message"; "lng_context_delete_msg" = "Delete Message"; "lng_context_select_msg" = "Select Message"; +"lng_context_pin_msg" = "Pin Message"; +"lng_context_unpin_msg" = "Unpin Message"; "lng_context_cancel_upload" = "Cancel Upload"; "lng_context_copy_selected" = "Copy Selected Text"; "lng_context_forward_selected" = "Forward Selected"; @@ -748,7 +805,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_send_image_too_large" = "Could not send a file, because it is larger than 1.5 GB :("; "lng_send_folder" = "Could not send «{name}» because it is a directory :("; -"lng_forward_choose" = "Choose recipient.."; +"lng_forward_choose" = "Choose recipient..."; "lng_forward_cant" = "Sorry, no way to forward here :("; "lng_forward_confirm" = "Forward to {recipient}?"; "lng_forward_share_contact" = "Share contact to {recipient}?"; @@ -768,6 +825,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_edit_group_title" = "Edit group name"; "lng_edit_contact_title" = "Edit contact name"; "lng_edit_channel_title" = "Edit channel"; +"lng_edit_sign_messages" = "Sign messages"; "lng_edit_group" = "Edit group"; "lng_edit_self_title" = "Edit your name"; "lng_confirm_contact_data" = "New Contact"; @@ -793,7 +851,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_selected_delete" = "Delete"; "lng_selected_forward" = "Forward"; "lng_selected_count" = "{count:_not_used_|# message|# messages}"; -"lng_selected_cancel_sure_this" = "Do you want to cancel this upload?"; +"lng_selected_cancel_sure_this" = "Cancel uploading?"; +"lng_selected_upload_stop" = "Stop"; "lng_selected_delete_sure_this" = "Do you want to delete this message?"; "lng_selected_delete_sure" = "Do you want to delete {count:_not_used_|# message|# messages}?"; "lng_box_delete" = "Delete"; @@ -809,7 +868,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_search_global_results" = "Global search results"; "lng_media_save_progress" = "{ready} of {total} {mb}"; -"lng_mediaview_save_as" = "Save As.."; +"lng_mediaview_save_as" = "Save As..."; "lng_mediaview_copy" = "Copy"; "lng_mediaview_forward" = "Forward"; "lng_mediaview_delete" = "Delete"; @@ -832,7 +891,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}"; "lng_new_version_minor" = "— Bug fixes and other minor improvements"; -"lng_new_version_text" = "GIF revolution: 10x faster sending and downloading, autoplay, save your favorite GIFs to a dedicated tab on the sticker panel.\n\nMore about GIFs:\n{gifs_link}\n\nInline bots: A new way to add bot content to any chat. Type a bot's username and your query in the text field to get instant results and send them to your chat partner. Try typing “@gif dog” in your next chat. Sample bots: @gif, @wiki, @bing, @vid, @bold.\n\nMore about inline bots:\n{bots_link}\n\nAlso in this release: New cute design for media, automatic download settings for photos, voice messages and GIFs."; +"lng_new_version_text" = "— Visual improvements"; "lng_menu_insert_unicode" = "Insert Unicode control character"; diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index a1f7a785cd..f1502116b2 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ defaultFontFamily: 'Open Sans'; semibold: 'Open Sans Semibold'; @@ -51,7 +51,10 @@ color7: #2996ad; // sea color8: #ce671b; // orange wndMinWidth: 380px; -wideModeWidth: 640px; + +adaptiveNormalWidth: 640px; +adaptiveWideWidth: 1366px; + wndMinHeight: 480px; wndDefWidth: 800px; wndDefHeight: 600px; @@ -64,6 +67,13 @@ layerBg: black; overBg: #edf2f5; +labelDefFlat: flatLabel { + font: font(fsize); + minWidth: 100px; + width: 0px; + align: align(left); +} + boxBg: white; boxVerticalMargin: 10px; boxWidth: 320px; @@ -72,6 +82,8 @@ boxPadding: margins(26px, 30px, 34px, 8px); boxMaxListHeight: 600px; boxFontSize: 14px; boxTextFont: font(boxFontSize); +boxLittleSkip: 10px; +boxMediumSkip: 20px; boxTitleFg: #444444; boxTitleFont: font(boxFontSize bold); @@ -123,6 +135,10 @@ redBoxLinkButton: linkButton(defaultBoxLinkButton) { overColor: #d15948; downColor: #db6352; } +boxLabel: flatLabel(labelDefFlat) { + font: font(boxFontSize); + align: align(topleft); +} defaultInputArea: InputArea { textFg: black; @@ -269,6 +285,21 @@ defaultPopupMenu: PopupMenu { widthMin: 180px; widthMax: 300px; } + +defaultTooltip: Tooltip { + textBg: #eef2f5; + textFg: #5d6c80; + textFont: normalFont; + textBorder: #c9d1db; + textPadding: margins(5px, 2px, 5px, 2px); + + shift: point(-20px, 20px); + skip: 10px; + + widthMax: 800px; + linesMax: 12; +} + almostTransparent: #ffffff0d; boxScroll: flatScroll(solidScroll) { width: 18px; @@ -593,13 +624,6 @@ scrollCountries: flatScroll(scrollDef) { lnkText: #0f7dc7; -labelDefFlat: flatLabel { - font: font(fsize); - minWidth: 100px; - width: 0px; - align: align(left); -} - introBtnTop: 288px; introSkip: 45px; introFinishSkip: 15px; @@ -937,6 +961,7 @@ dlgActiveUnreadColor: #5b94bf; dlgActiveUnreadBG: white; dlgActiveColor: white; dlgActiveDateColor: #d3e2ee; +dlgActiveUnreadMutedBG: dlgActiveDateColor; topBarHeight: 54px; topBarBG: white; @@ -950,6 +975,21 @@ topBarBackAlpha: 0.8; topBarBackImg: sprite(65px, 112px, 9px, 16px); topBarBackColor: #005faf; topBarBackFont: font(16px); +topBarSearch: iconedButton(btnDefIconed) { + bgColor: transparent; + overBgColor: transparent; + + icon: sprite(84px, 374px, 18px, 18px); + iconPos: point(13px, 18px); + downIcon: sprite(84px, 374px, 18px, 18px); + downIconPos: point(13px, 18px); + + opacity: 0.22; + overOpacity: 0.36; + + width: 44px; + height: topBarHeight; +} topBarMinPadding: 5px; topBarButton: flatButton(btnDefFlat) { color: btnYesColor; @@ -1009,10 +1049,11 @@ msgServiceNameFont: semiboldFont; msgServicePhotoWidth: 100px; msgDateFont: font(13px); msgMinWidth: 190px; -msgPhotoSize: 30px; +msgPhotoSize: 33px; msgPhotoSkip: 40px; msgPadding: margins(13px, 7px, 13px, 8px); -msgMargin: margins(13px, 4px, 53px, 4px); +msgMargin: margins(13px, 10px, 53px, 2px); +msgMarginTopAttached: 3px; msgLnkPadding: 2px; // for media open / save links msgBorder: #f0f0f0; msgInBg: #fff; @@ -1044,11 +1085,27 @@ msgInReplyBarColor: #2fa9e2; msgOutReplyBarSelColor: #4da79f; msgInReplyBarSelColor: #2fa9e2; +msgBotKbDuration: 200; +msgBotKbFont: semiboldFont; +msgBotKbOverOpacity: 0.1; +msgBotKbIconPadding: 2px; +msgBotKbUrlIcon: sprite(188px, 338px, 10px, 10px); +msgBotKbCallbackIcon: msgBotKbUrlIcon; +msgBotKbRequestPhoneIcon: msgBotKbUrlIcon; +msgBotKbRequestLocationIcon: msgBotKbUrlIcon; +msgBotKbButton: botKeyboardButton { + margin: 5px; + padding: 10px; + height: 36px; + textTop: 8px; + downTextTop: 9px; +} + msgServiceBg: #89a0b47f; msgServiceSelectBg: #bbc8d4a2; msgServiceColor: #FFF; msgServicePadding: margins(12px, 3px, 12px, 4px); -msgServiceMargin: margins(10px, 7px, 80px, 7px); +msgServiceMargin: margins(10px, 10px, 80px, 2px); msgColor: #000; msgDateColor: #000; @@ -1107,7 +1164,7 @@ collapseButton: flatButton(btnDefFlat) { textTop: 3px; overTextTop: 3px; downTextTop: 3px; - height: 24px; + height: 25px; } collapseHideDuration: 200; collapseShowDuration: 200; @@ -1144,6 +1201,24 @@ outTextStyle: textStyle(defaultTextStyle) { selectBg: msgOutBgSelected; selectOverlay: msgSelectOverlay; } +inFwdTextStyle: textStyle(defaultTextStyle) { + linkFlags: semiboldFont; + linkFlagsOver: semiboldFont; + linkFg: msgInServiceFg; + linkFgDown: msgInServiceFg; +} +outFwdTextStyle: textStyle(inFwdTextStyle) { + linkFg: msgOutServiceFg; + linkFgDown: msgOutServiceFg; +} +inFwdTextStyleSelected: textStyle(inFwdTextStyle) { + linkFg: msgInServiceFgSelected; + linkFgDown: msgInServiceFgSelected; +} +outFwdTextStyleSelected: textStyle(inFwdTextStyle) { + linkFg: msgOutServiceFgSelected; + linkFgDown: msgOutServiceFgSelected; +} medviewSaveAsTextStyle: textStyle(defaultTextStyle) { linkFg: #91d9ff; linkFgDown: #91d9ff; @@ -1265,11 +1340,21 @@ msgFileBlue: sprite(60px, 425px, 20px, 20px); msgFileOverDuration: 200; msgFileRadialLine: 3px; -msgFileExtPadding: 8px; -msgFileExtTop: 30px; - msgVideoSize: size(320px, 240px); +msgWaveformBar: 2px; +msgWaveformSkip: 1px; +msgWaveformMin: 2px; +msgWaveformMax: 20px; +msgWaveformInActive: #59b6eb; +msgWaveformInActiveSelected: #51a3d3; +msgWaveformInInactive: #d4dee6; +msgWaveformInInactiveSelected: #9cc1e1; +msgWaveformOutActive: #78c67f; +msgWaveformOutActiveSelected: #6badad; +msgWaveformOutInactive: #b3e2b4; +msgWaveformOutInactiveSelected: #91c3c3; + sendPadding: 9px; btnSend: flatButton(btnDefFlat) { color: btnYesColor; @@ -1349,7 +1434,7 @@ broadcastToggle: flatCheckbox { bgColor: white; disColor: black; - width: 36px; + width: 34px; height: 46px; duration: 200; bgFunc: transition(easeOutCirc); @@ -1364,13 +1449,23 @@ broadcastToggle: flatCheckbox { disImageRect: sprite(18px, 125px, 22px, 21px); chkDisImageRect: sprite(18px, 125px, 22px, 21px); - imagePos: point(7px, 12px); + imagePos: point(6px, 12px); +} +silentToggle: flatCheckbox(broadcastToggle) { + width: 33px; + + imageRect: sprite(354px, 242px, 21px, 21px); + chkImageRect: sprite(354px, 221px, 21px, 21px); + overImageRect: sprite(375px, 242px, 21px, 21px); + chkOverImageRect: sprite(375px, 221px, 21px, 21px); + disImageRect: sprite(354px, 242px, 21px, 21px); + chkDisImageRect: sprite(354px, 221px, 21px, 21px); } btnRecordAudio: sprite(379px, 390px, 16px, 24px); btnRecordAudioActive: sprite(379px, 366px, 16px, 24px); recordSignalColor: #f17077; recordSignalMin: 5px; -recordSignalMax: 10px; +recordSignalMax: 12px; recordCancel: #aaa; recordCancelActive: #ec6466; recordFont: font(13px); @@ -1383,6 +1478,7 @@ replyTop: 8px; replyBottom: 6px; replyIconPos: point(13px, 13px); replyIcon: sprite(343px, 197px, 24px, 24px); +editIcon: sprite(371px, 286px, 24px, 24px); replyCancel: iconedButton(btnDefIconed) { icon: sprite(165px, 24px, 14px, 14px); iconPos: point(17px, 17px); @@ -1447,6 +1543,7 @@ reportSpamBg: #fffffff0; newMsgSound: ':/gui/art/newmsg.wav'; unreadBarHeight: 32px; +unreadBarMargin: 8px; unreadBarFont: semiboldFont; unreadBarBG: #fcfbfa; unreadBarBorder: shadowColor; @@ -2017,17 +2114,17 @@ verifiedCheckInv: sprite(299px, 221px, 14px, 14px); verifiedCheckPos: point(4px, 2px); botKbDuration: 200; -botKbBg: #f7f7f7; -botKbOverBg: #e8ecef; -botKbDownBg: #dfe3e6; -botKbColor: #8a8a8f; -botKbFont: font(16px); +botKbBg: #edf1f5; +botKbOverBg: #d8e2ec; +botKbDownBg: #d8e2ec; +botKbColor: #4b565f; +botKbFont: font(15px semibold); botKbButton: botKeyboardButton { margin: 10px; padding: 10px; - height: 36px; - textTop: 8px; - downTextTop: 9px; + height: 38px; + textTop: 9px; + downTextTop: 10px; } botKbTinyButton: botKeyboardButton { margin: 4px; @@ -2045,6 +2142,7 @@ minPhotoSize: 100px; maxMediaSize: 420px; maxStickerSize: 256px; maxGifSize: 320px; +maxSignatureSize: 144px; mvBgColor: #222; mvBgOpacity: 0.92; @@ -2180,6 +2278,9 @@ overviewFileStatusTop: 27px; overviewFileDateTop: 49px; overviewFileChecked: #2fa9e2; overviewFileCheck: #00000066; +overviewFileExtPadding: 5px; +overviewFileExtTop: 24px; +overviewFileExtFont: font(18px semibold); // Mac specific @@ -2376,7 +2477,7 @@ linksDateMargin: margins(0px, 15px, 0px, 2px); linksPhotoCheck: sprite(184px, 196px, 16px, 16px); linksPhotoChecked: sprite(168px, 196px, 16px, 16px); -inlineResultsLeft: 15px; +inlineResultsLeft: 11px; inlineResultsSkip: 3px; inlineMediaHeight: 96px; inlineThumbSize: 64px; @@ -2385,5 +2486,12 @@ inlineDescriptionFg: #8a8a8a; inlineRowMargin: 6px; inlineRowBorder: linksBorder; inlineRowBorderFg: linksBorderFg; +inlineRowFileNameTop: 2px; +inlineRowFileDescriptionTop: 23px; inlineResultsMinWidth: 64px; inlineDurationMargin: 3px; + +editTextArea: InputArea(defaultInputArea) { + textMargins: margins(1px, 6px, 1px, 4px); + heightMax: 256px; +} diff --git a/Telegram/Resources/style_classes.txt b/Telegram/Resources/style_classes.txt index 611b367a41..229b3398bb 100644 --- a/Telegram/Resources/style_classes.txt +++ b/Telegram/Resources/style_classes.txt @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ textStyle { linkFlags: font; @@ -272,6 +272,20 @@ PopupMenu { widthMax: number; } +Tooltip { + textBg: color; + textFg: color; + textFont: font; + textBorder: color; + textPadding: margins; + + shift: point; + skip: number; + + widthMax: number; + linesMax: number; +} + botKeyboardButton { margin: number; padding: number; diff --git a/Telegram/SourceFiles/_other/genemoji.cpp b/Telegram/SourceFiles/_other/genemoji.cpp index 5e6fce4bf5..22e3fd1bd3 100644 --- a/Telegram/SourceFiles/_other/genemoji.cpp +++ b/Telegram/SourceFiles/_other/genemoji.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "genemoji.h" @@ -1942,7 +1942,7 @@ In addition, as a special exception, the copyright holders give permission\n\ to link the code of portions of this program with the OpenSSL library.\n\ \n\ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\ -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ */\n"; tcpp << "#include \"stdafx.h\"\n#include \"gui/emoji_config.h\"\n\n"; diff --git a/Telegram/SourceFiles/_other/genemoji.h b/Telegram/SourceFiles/_other/genemoji.h index 61c0e3e767..ecf35ef48e 100644 --- a/Telegram/SourceFiles/_other/genemoji.h +++ b/Telegram/SourceFiles/_other/genemoji.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include #include diff --git a/Telegram/SourceFiles/_other/genlang.cpp b/Telegram/SourceFiles/_other/genlang.cpp index 60e77ec79d..614f81de5b 100644 --- a/Telegram/SourceFiles/_other/genlang.cpp +++ b/Telegram/SourceFiles/_other/genlang.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "genlang.h" @@ -189,7 +189,7 @@ void readKeyValue(const char *&from, const char *end) { if (*from == ':') { start = ++from; - + QVector &counted(keysCounted[varName][tagName]); QByteArray subvarValue; bool foundtag = false; @@ -391,7 +391,7 @@ bool genLang(const QString &lang_in, const QString &lang_out) { th.setCodec("ISO 8859-1"); th << "\ /*\n\ -Created from \'/Resources/lang.txt\' by \'/MetaLang\' project\n\ +Created from \'/Resources/lang.strings\' by \'/MetaLang\' project\n\ \n\ WARNING! All changes made in this file will be lost!\n\ \n\ @@ -412,7 +412,7 @@ In addition, as a special exception, the copyright holders give permission\n\ to link the code of portions of this program with the OpenSSL library.\n\ \n\ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\ -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ */\n"; th << "#pragma once\n\n"; @@ -475,7 +475,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ tcpp << "\ /*\n\ -Created from \'/Resources/lang.txt\' by \'/MetaLang\' project\n\ +Created from \'/Resources/lang.strings\' by \'/MetaLang\' project\n\ \n\ WARNING! All changes made in this file will be lost!\n\ \n\ @@ -496,7 +496,7 @@ In addition, as a special exception, the copyright holders give permission\n\ to link the code of portions of this program with the OpenSSL library.\n\ \n\ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\ -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ */\n"; tcpp << "#include \"stdafx.h\"\n#include \"lang.h\"\n\n"; tcpp << "namespace {\n"; @@ -606,13 +606,22 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ ++depth; current += ich; - if (tag == current) { + bool exact = (tag == current); + if (exact) { tcpp << tab.repeated(depth + 1) << "if (ch + " << depth << " == e) {\n"; tcpp << tab.repeated(depth + 1) << "\treturn lt_" << tag << ";\n"; tcpp << tab.repeated(depth + 1) << "}\n"; } - tcpp << tab.repeated(depth + 1) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n"; + QByteArray nexttag = j.key(); + if (exact && depth > 0 && nexttag.mid(0, depth) != current) { + current.chop(1); + --depth; + tcpp << tab.repeated(depth + 1) << "break;\n"; + break; + } else { + tcpp << tab.repeated(depth + 1) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n"; + } } while (true); ++j; } @@ -637,7 +646,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ tcpp << "\tswitch (*(ch + " << depth << ")) {\n"; for (LangKeys::const_iterator i = keys.cbegin(), j = i + 1, e = keys.cend(); i != e; ++i) { QByteArray key = i.key(); - while (key.mid(0, depth) != current) { + while (depth > 0 && key.mid(0, depth) != current) { tcpp << tab.repeated(depth - 3) << "}\n"; current.chop(1); --depth; @@ -645,7 +654,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ } do { if (key == current) break; - + char ich = i.key().at(current.size()); tcpp << tab.repeated(current.size() - 3) << "case '" << ich << "':\n"; if (j == e || ich != ((j.key().size() > depth) ? j.key().at(depth) : 0)) { @@ -661,13 +670,22 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ ++depth; current += ich; - if (key == current) { + bool exact = (key == current); + if (exact) { tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " == e) {\n"; tcpp << tab.repeated(depth - 3) << "\treturn " << key << (keysTags[key].isEmpty() ? "" : "__tagged") << ";\n"; tcpp << tab.repeated(depth - 3) << "}\n"; } - tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n"; + QByteArray nextkey = j.key(); + if (exact && depth > 0 && nextkey.mid(0, depth) != current) { + current.chop(1); + --depth; + tcpp << tab.repeated(depth - 3) << "break;\n"; + break; + } else { + tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n"; + } } while (true); ++j; } @@ -707,16 +725,25 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ tcpp << "\tif (index >= lngtags_max_counted_values) return lngkeys_cnt;\n\n"; if (!tags.isEmpty()) { tcpp << "\tswitch (key) {\n"; - for (int i = 0, l = keysOrder.size(); i < l; ++i) { - QVector &tagsList(keysTags[keysOrder[i]]); + for (auto key : keysOrder) { + QVector &tagsList(keysTags[key]); if (tagsList.isEmpty()) continue; - QMap > &countedTags(keysCounted[keysOrder[i]]); - tcpp << "\tcase " << keysOrder[i] << "__tagged: {\n"; + QMap > &countedTags(keysCounted[key]); + bool hasCounted = false; + for (auto tag : tagsList) { + if (!countedTags[tag].isEmpty()) { + hasCounted = true; + break; + } + } + if (!hasCounted) continue; + + tcpp << "\tcase " << key << "__tagged: {\n"; tcpp << "\t\tswitch (tag) {\n"; - for (int j = 0, s = tagsList.size(); j < s; ++j) { - if (!countedTags[tagsList[j]].isEmpty()) { - tcpp << "\t\tcase lt_" << tagsList[j] << ": return LangKey(" << keysOrder[i] << "__" << tagsList[j] << "0 + index);\n"; + for (auto tag : tagsList) { + if (!countedTags[tag].isEmpty()) { + tcpp << "\t\tcase lt_" << tag << ": return LangKey(" << key << "__" << tag << "0 + index);\n"; } } tcpp << "\t\t}\n"; @@ -724,7 +751,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ } tcpp << "\t}\n\n"; } - tcpp << "\treturn lngkeys_cnt;"; + tcpp << "\treturn lngkeys_cnt;\n"; tcpp << "}\n\n"; tcpp << "bool LangLoader::feedKeyValue(LangKey key, const QString &value) {\n"; diff --git a/Telegram/SourceFiles/_other/genlang.h b/Telegram/SourceFiles/_other/genlang.h index dadeb8c09b..b88f6d0306 100644 --- a/Telegram/SourceFiles/_other/genlang.h +++ b/Telegram/SourceFiles/_other/genlang.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include #include diff --git a/Telegram/SourceFiles/_other/genstyles.cpp b/Telegram/SourceFiles/_other/genstyles.cpp index 709117d522..55fe18a87b 100644 --- a/Telegram/SourceFiles/_other/genstyles.cpp +++ b/Telegram/SourceFiles/_other/genstyles.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "genstyles.h" @@ -381,9 +381,9 @@ In addition, as a special exception, the copyright holders give permission\n\ to link the code of portions of this program with the OpenSSL library.\n\ \n\ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\ -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ */\n"; - tout << "#pragma once\n\n#include \"style.h\"\n\nnamespace style {\n"; + tout << "#pragma once\n\n#include \"gui/style.h\"\n\nnamespace style {\n"; for (int i = 0, l = byIndex.size(); i < l; ++i) { ClassData &cls(byIndex[i]); classes.insert(cls.name, cls); @@ -1542,9 +1542,9 @@ In addition, as a special exception, the copyright holders give permission\n\ to link the code of portions of this program with the OpenSSL library.\n\ \n\ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\ -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ */\n"; - tout << "#pragma once\n\n#include \"style.h\"\n\nnamespace st {\n"; + tout << "#pragma once\n\n#include \"gui/style.h\"\n\nnamespace st {\n"; tcpp << "\ /*\n\ Created from \'/Resources/style.txt\' by \'/MetaStyle\' project\n\ @@ -1568,7 +1568,7 @@ In addition, as a special exception, the copyright holders give permission\n\ to link the code of portions of this program with the OpenSSL library.\n\ \n\ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\ -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ */\n"; tcpp << "#include \"stdafx.h\"\n#include \"style_auto.h\"\n\nnamespace {\n"; for (int i = 0, l = scalars.size(); i < l; ++i) { @@ -1945,7 +1945,7 @@ In addition, as a special exception, the copyright holders give permission\n\ to link the code of portions of this program with the OpenSSL library.\n\ \n\ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\ -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org\n\ +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ */\n"; tnum << "#include \"stdafx.h\"\n#include \"numbers.h\"\n\n"; tnum << "QVector phoneNumberParse(const QString &number) {\n"; diff --git a/Telegram/SourceFiles/_other/genstyles.h b/Telegram/SourceFiles/_other/genstyles.h index 40a6d6ce67..05fa8c333e 100644 --- a/Telegram/SourceFiles/_other/genstyles.h +++ b/Telegram/SourceFiles/_other/genstyles.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include #include diff --git a/Telegram/SourceFiles/_other/memain.cpp b/Telegram/SourceFiles/_other/memain.cpp index 6daade312c..aaf5b63415 100644 --- a/Telegram/SourceFiles/_other/memain.cpp +++ b/Telegram/SourceFiles/_other/memain.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "memain.h" diff --git a/Telegram/SourceFiles/_other/memain.h b/Telegram/SourceFiles/_other/memain.h index c2faceb850..c3293a73c8 100644 --- a/Telegram/SourceFiles/_other/memain.h +++ b/Telegram/SourceFiles/_other/memain.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include diff --git a/Telegram/SourceFiles/_other/mlmain.cpp b/Telegram/SourceFiles/_other/mlmain.cpp index 490ce50efe..42c01b0d28 100644 --- a/Telegram/SourceFiles/_other/mlmain.cpp +++ b/Telegram/SourceFiles/_other/mlmain.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "mlmain.h" diff --git a/Telegram/SourceFiles/_other/mlmain.h b/Telegram/SourceFiles/_other/mlmain.h index a909bb5025..6516c86b25 100644 --- a/Telegram/SourceFiles/_other/mlmain.h +++ b/Telegram/SourceFiles/_other/mlmain.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include diff --git a/Telegram/SourceFiles/_other/msmain.cpp b/Telegram/SourceFiles/_other/msmain.cpp index cd31683884..ac85f52e41 100644 --- a/Telegram/SourceFiles/_other/msmain.cpp +++ b/Telegram/SourceFiles/_other/msmain.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "msmain.h" #include diff --git a/Telegram/SourceFiles/_other/msmain.h b/Telegram/SourceFiles/_other/msmain.h index f0e13771a2..4db6725527 100644 --- a/Telegram/SourceFiles/_other/msmain.h +++ b/Telegram/SourceFiles/_other/msmain.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include diff --git a/Telegram/SourceFiles/_other/packer.cpp b/Telegram/SourceFiles/_other/packer.cpp index 7cf2da9552..e2a6f128af 100644 --- a/Telegram/SourceFiles/_other/packer.cpp +++ b/Telegram/SourceFiles/_other/packer.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "packer.h" diff --git a/Telegram/SourceFiles/_other/packer.h b/Telegram/SourceFiles/_other/packer.h index b82ccd71c2..ccdff11cf8 100644 --- a/Telegram/SourceFiles/_other/packer.h +++ b/Telegram/SourceFiles/_other/packer.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/_other/updater.cpp b/Telegram/SourceFiles/_other/updater.cpp index 103803a882..f70e65ca87 100644 --- a/Telegram/SourceFiles/_other/updater.cpp +++ b/Telegram/SourceFiles/_other/updater.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "updater.h" @@ -255,7 +255,7 @@ bool update() { } else { break; } - } while (copyTries < 30); + } while (copyTries < 100); if (!copyResult) { writeLog(L"Error: failed to copy, asking to retry.."); WCHAR errMsg[2048]; @@ -328,14 +328,11 @@ void updateRegistry() { } } -#include - int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdParamarg, int cmdShow) { openLog(); -#ifdef _NEED_WIN_GENERATE_DUMP _oldWndExceptionFilter = SetUnhandledExceptionFilter(_exceptionFilter); -#endif +// CAPIHook apiHook("kernel32.dll", "SetUnhandledExceptionFilter", (PROC)RedirectedSetUnhandledExceptionFilter); writeLog(L"Updaters started.."); @@ -459,13 +456,12 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara ShellExecute(0, 0, (updateTo + L"Telegram.exe").c_str(), (L"-noupdate" + targs).c_str(), 0, SW_SHOWNORMAL); } - writeLog(L"Executed Telegram.exe, closing log and quiting.."); + writeLog(L"Executed Telegram.exe, closing log and quitting.."); closeLog(); return 0; } -#ifdef _NEED_WIN_GENERATE_DUMP static const WCHAR *_programName = L"Telegram Desktop"; // folder in APPDATA, if current path is unavailable for writing static const WCHAR *_exeName = L"Updater.exe"; @@ -486,13 +482,18 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) { static const int maxFileLen = MAX_PATH * 10; WCHAR szPath[maxFileLen]; - wsprintf(szPath, L"%stdumps\\", path); - + wsprintf(szPath, L"%stdata\\", path); if (!CreateDirectory(szPath, NULL)) { if (GetLastError() != ERROR_ALREADY_EXISTS) { return 0; } } + wsprintf(szPath, L"%sdumps\\", path); + if (!CreateDirectory(szPath, NULL)) { + if (GetLastError() != ERROR_ALREADY_EXISTS) { + return 0; + } + } WCHAR szFileName[maxFileLen]; WCHAR szExeName[maxFileLen]; @@ -507,10 +508,10 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) { GetLocalTime(&stLocalTime); - wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", - szPath, szExeName, updaterVersionStr, - stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, - stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, + wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", + szPath, szExeName, updaterVersionStr, + stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, + stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, GetCurrentProcessId(), GetCurrentThreadId()); return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); } @@ -546,7 +547,7 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) { hDumpFile = _generateDumpFileAtPath(wstrPath); } } - + if (!hDumpFile || hDumpFile == INVALID_HANDLE_VALUE) { return; } @@ -564,4 +565,10 @@ LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers) { return _oldWndExceptionFilter ? (*_oldWndExceptionFilter)(pExceptionPointers) : EXCEPTION_CONTINUE_SEARCH; } -#endif +// see http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li +LPTOP_LEVEL_EXCEPTION_FILTER WINAPI RedirectedSetUnhandledExceptionFilter(_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) { + // When the CRT calls SetUnhandledExceptionFilter with NULL parameter + // our handler will not get removed. + _oldWndExceptionFilter = lpTopLevelExceptionFilter; + return 0; +} diff --git a/Telegram/SourceFiles/_other/updater.h b/Telegram/SourceFiles/_other/updater.h index 6afd341a33..8b2709c272 100644 --- a/Telegram/SourceFiles/_other/updater.h +++ b/Telegram/SourceFiles/_other/updater.h @@ -16,13 +16,19 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once #include #include + +#pragma warning(push) +#pragma warning(disable:4091) #include +#include +#pragma warning(pop) + #include #include @@ -32,12 +38,9 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org using std::deque; using std::wstring; -#define _NEED_WIN_GENERATE_DUMP - -#ifdef _NEED_WIN_GENERATE_DUMP extern LPTOP_LEVEL_EXCEPTION_FILTER _oldWndExceptionFilter; LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers); -#endif _NEED_WIN_GENERATE_DUMP +LPTOP_LEVEL_EXCEPTION_FILTER WINAPI RedirectedSetUnhandledExceptionFilter(_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); static int updaterVersion = 1000; static const WCHAR *updaterVersionStr = L"0.1.0"; diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp index 35d67072c9..7ae72c3709 100644 --- a/Telegram/SourceFiles/_other/updater_linux.cpp +++ b/Telegram/SourceFiles/_other/updater_linux.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include #include @@ -324,7 +324,7 @@ bool update() { int main(int argc, char *argv[]) { bool needupdate = true, autostart = false, debug = false, tosettings = false, startintray = false, testmode = false; - char *key = 0; + char *key = 0, *crashreport = 0; for (int i = 1; i < argc; ++i) { if (equal(argv[i], "-noupdate")) { needupdate = false; @@ -342,7 +342,9 @@ int main(int argc, char *argv[]) { key = argv[i]; } else if (equal(argv[i], "-workpath") && ++i < argc) { workDir = argv[i]; - } + } else if (equal(argv[i], "-crashreport") && ++i < argc) { + crashreport = argv[i]; + } } openLog(); @@ -408,24 +410,27 @@ int main(int argc, char *argv[]) { char *args[MaxArgsCount] = {0}, p_noupdate[] = "-noupdate", p_autostart[] = "-autostart", p_debug[] = "-debug", p_tosettings[] = "-tosettings", p_key[] = "-key", p_startintray[] = "-startintray", p_testmode[] = "-testmode"; int argIndex = 0; args[argIndex++] = path; - args[argIndex++] = p_noupdate; - if (autostart) args[argIndex++] = p_autostart; - if (debug) args[argIndex++] = p_debug; - if (startintray) args[argIndex++] = p_startintray; - if (testmode) args[argIndex++] = p_testmode; - if (tosettings) args[argIndex++] = p_tosettings; - if (key) { - args[argIndex++] = p_key; - args[argIndex++] = key; - } - + if (crashreport) { + args[argIndex++] = crashreport; + } else { + args[argIndex++] = p_noupdate; + if (autostart) args[argIndex++] = p_autostart; + if (debug) args[argIndex++] = p_debug; + if (startintray) args[argIndex++] = p_startintray; + if (testmode) args[argIndex++] = p_testmode; + if (tosettings) args[argIndex++] = p_tosettings; + if (key) { + args[argIndex++] = p_key; + args[argIndex++] = key; + } + } pid_t pid = fork(); switch (pid) { case -1: writeLog("fork() failed!"); return 1; case 0: execv(path, args); return 1; } - writeLog("Executed Telegram, closing log and quiting.."); + writeLog("Executed Telegram, closing log and quitting.."); closeLog(); return 0; diff --git a/Telegram/SourceFiles/_other/updater_osx.m b/Telegram/SourceFiles/_other/updater_osx.m index 4c3e7c4fbb..ef13dfc065 100644 --- a/Telegram/SourceFiles/_other/updater_osx.m +++ b/Telegram/SourceFiles/_other/updater_osx.m @@ -16,13 +16,14 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #import NSString *appName = @"Telegram.app"; NSString *appDir = nil; NSString *workDir = nil; +NSString *crashReportArg = nil; #ifdef _DEBUG BOOL _debug = YES; @@ -101,6 +102,10 @@ int main(int argc, const char * argv[]) { [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; procId = [[formatter numberFromString:[NSString stringWithUTF8String:argv[i]]] intValue]; } + } else if ([@"-crashreport" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { + if (++i < argc) { + crashReportArg = [NSString stringWithUTF8String:argv[i]]; + } } else if ([@"-noupdate" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { update = NO; } else if ([@"-tosettings" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) { @@ -214,15 +219,17 @@ int main(int argc, const char * argv[]) { } NSString *appPath = [[NSArray arrayWithObjects:appDir, appRealName, nil] componentsJoinedByString:@""]; - NSMutableArray *args = [[NSMutableArray alloc] initWithObjects:@"-noupdate", nil]; - if (toSettings) [args addObject:@"-tosettings"]; - if (_debug) [args addObject:@"-debug"]; - if (startInTray) [args addObject:@"-startintray"]; - if (testMode) [args addObject:@"-testmode"]; - if (autoStart) [args addObject:@"-autostart"]; - if (key) { - [args addObject:@"-key"]; - [args addObject:key]; + NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: crashReportArg ? crashReportArg : @"-noupdate", nil]; + if (!crashReportArg) { + if (toSettings) [args addObject:@"-tosettings"]; + if (_debug) [args addObject:@"-debug"]; + if (startInTray) [args addObject:@"-startintray"]; + if (testMode) [args addObject:@"-testmode"]; + if (autoStart) [args addObject:@"-autostart"]; + if (key) { + [args addObject:@"-key"]; + [args addObject:key]; + } } writeLog([[NSArray arrayWithObjects:@"Running application '", appPath, @"' with args '", [args componentsJoinedByString:@"' '"], @"'..", nil] componentsJoinedByString:@""]); NSError *error = nil; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 46b95ce1c3..af7408859d 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -16,10 +16,10 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" +#include "gui/style.h" #include "lang.h" #include "application.h" @@ -29,89 +29,65 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "localstorage.h" -ApiWrap::ApiWrap(QObject *parent) : QObject(parent) { +ApiWrap::ApiWrap(QObject *parent) : QObject(parent) +, _messageDataResolveDelayed(new SingleDelayedCall(this, "resolveMessageDatas")) { App::initBackground(); - connect(&_replyToTimer, SIGNAL(timeout()), this, SLOT(resolveReplyTo())); connect(&_webPagesTimer, SIGNAL(timeout()), this, SLOT(resolveWebPages())); } void ApiWrap::init() { } -void ApiWrap::itemRemoved(HistoryItem *item) { - if (HistoryReply *reply = item->toHistoryReply()) { - ChannelData *channel = reply->history()->peer->asChannel(); - ReplyToRequests *requests(replyToRequests(channel, true)); - if (requests) { - ReplyToRequests::iterator i = requests->find(reply->replyToId()); - if (i != requests->cend()) { - for (QList::iterator j = i->replies.begin(); j != i->replies.end();) { - if ((*j) == reply) { - j = i->replies.erase(j); - } else { - ++j; - } - } - if (i->replies.isEmpty()) { - requests->erase(i); - } - } - if (channel && requests->isEmpty()) { - _channelReplyToRequests.remove(channel); - } - } - } +void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback *callback) { + MessageDataRequest::CallbackPtr pcallback(callback); + MessageDataRequest &req(channel ? _channelMessageDataRequests[channel][msgId] : _messageDataRequests[msgId]); + req.callbacks.append(pcallback); + if (!req.req) _messageDataResolveDelayed->call(); } -void ApiWrap::requestReplyTo(HistoryReply *reply, ChannelData *channel, MsgId id) { - ReplyToRequest &req(channel ? _channelReplyToRequests[channel][id] : _replyToRequests[id]); - req.replies.append(reply); - if (!req.req) _replyToTimer.start(1); -} - -ApiWrap::MessageIds ApiWrap::collectMessageIds(const ReplyToRequests &requests) { +ApiWrap::MessageIds ApiWrap::collectMessageIds(const MessageDataRequests &requests) { MessageIds result; result.reserve(requests.size()); - for (ReplyToRequests::const_iterator i = requests.cbegin(), e = requests.cend(); i != e; ++i) { + for (MessageDataRequests::const_iterator i = requests.cbegin(), e = requests.cend(); i != e; ++i) { if (i.value().req > 0) continue; result.push_back(MTP_int(i.key())); } return result; } -ApiWrap::ReplyToRequests *ApiWrap::replyToRequests(ChannelData *channel, bool onlyExisting) { +ApiWrap::MessageDataRequests *ApiWrap::messageDataRequests(ChannelData *channel, bool onlyExisting) { if (channel) { - ChannelReplyToRequests::iterator i = _channelReplyToRequests.find(channel); - if (i == _channelReplyToRequests.cend()) { + ChannelMessageDataRequests::iterator i = _channelMessageDataRequests.find(channel); + if (i == _channelMessageDataRequests.cend()) { if (onlyExisting) return 0; - i = _channelReplyToRequests.insert(channel, ReplyToRequests()); + i = _channelMessageDataRequests.insert(channel, MessageDataRequests()); } return &i.value(); } - return &_replyToRequests; + return &_messageDataRequests; } -void ApiWrap::resolveReplyTo() { - if (_replyToRequests.isEmpty() && _channelReplyToRequests.isEmpty()) return; +void ApiWrap::resolveMessageDatas() { + if (_messageDataRequests.isEmpty() && _channelMessageDataRequests.isEmpty()) return; - MessageIds ids = collectMessageIds(_replyToRequests); + MessageIds ids = collectMessageIds(_messageDataRequests); if (!ids.isEmpty()) { - mtpRequestId req = MTP::send(MTPmessages_GetMessages(MTP_vector(ids)), rpcDone(&ApiWrap::gotReplyTo, (ChannelData*)0), RPCFailHandlerPtr(), 0, 5); - for (ReplyToRequests::iterator i = _replyToRequests.begin(); i != _replyToRequests.cend(); ++i) { + mtpRequestId req = MTP::send(MTPmessages_GetMessages(MTP_vector(ids)), rpcDone(&ApiWrap::gotMessageDatas, (ChannelData*)nullptr), RPCFailHandlerPtr(), 0, 5); + for (MessageDataRequests::iterator i = _messageDataRequests.begin(); i != _messageDataRequests.cend(); ++i) { if (i.value().req > 0) continue; i.value().req = req; } } - for (ChannelReplyToRequests::iterator j = _channelReplyToRequests.begin(); j != _channelReplyToRequests.cend();) { + for (ChannelMessageDataRequests::iterator j = _channelMessageDataRequests.begin(); j != _channelMessageDataRequests.cend();) { if (j->isEmpty()) { - j = _channelReplyToRequests.erase(j); + j = _channelMessageDataRequests.erase(j); continue; } MessageIds ids = collectMessageIds(j.value()); if (!ids.isEmpty()) { - mtpRequestId req = MTP::send(MTPchannels_GetMessages(j.key()->inputChannel, MTP_vector(ids)), rpcDone(&ApiWrap::gotReplyTo, j.key()), RPCFailHandlerPtr(), 0, 5); - for (ReplyToRequests::iterator i = j->begin(); i != j->cend(); ++i) { + mtpRequestId req = MTP::send(MTPchannels_GetMessages(j.key()->inputChannel, MTP_vector(ids)), rpcDone(&ApiWrap::gotMessageDatas, j.key()), RPCFailHandlerPtr(), 0, 5); + for (MessageDataRequests::iterator i = j->begin(); i != j->cend(); ++i) { if (i.value().req > 0) continue; i.value().req = req; } @@ -120,7 +96,7 @@ void ApiWrap::resolveReplyTo() { } } -void ApiWrap::gotReplyTo(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) { +void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) { switch (msgs.type()) { case mtpc_messages_messages: { const MTPDmessages_messages &d(msgs.c_messages_messages()); @@ -141,10 +117,10 @@ void ApiWrap::gotReplyTo(ChannelData *channel, const MTPmessages_Messages &msgs, if (channel) { channel->ptsReceived(d.vpts.v); } else { - LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotReplyTo)")); + LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotDependencyItem)")); } if (d.has_collapsed()) { // should not be returned - LOG(("API Error: channels.getMessages and messages.getMessages should not return collapsed groups! (ApiWrap::gotReplyTo)")); + LOG(("API Error: channels.getMessages and messages.getMessages should not return collapsed groups! (ApiWrap::gotDependencyItem)")); } App::feedUsers(d.vusers); @@ -152,16 +128,12 @@ void ApiWrap::gotReplyTo(ChannelData *channel, const MTPmessages_Messages &msgs, App::feedMsgs(d.vmessages, NewMessageExisting); } break; } - ReplyToRequests *requests(replyToRequests(channel, true)); + MessageDataRequests *requests(messageDataRequests(channel, true)); if (requests) { - for (ReplyToRequests::iterator i = requests->begin(); i != requests->cend();) { + for (MessageDataRequests::iterator i = requests->begin(); i != requests->cend();) { if (i.value().req == req) { - for (QList::const_iterator j = i.value().replies.cbegin(), e = i.value().replies.cend(); j != e; ++j) { - if (*j) { - (*j)->updateReplyTo(true); - } else { - App::main()->updateReplyTo(); - } + for (MessageDataRequest::Callbacks::const_iterator j = i.value().callbacks.cbegin(), e = i.value().callbacks.cend(); j != e; ++j) { + (*j)->call(channel, i.key()); } i = requests->erase(i); } else { @@ -169,7 +141,7 @@ void ApiWrap::gotReplyTo(ChannelData *channel, const MTPmessages_Messages &msgs, } } if (channel && requests->isEmpty()) { - _channelReplyToRequests.remove(channel); + _channelMessageDataRequests.remove(channel); } } } @@ -258,7 +230,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt } if (f.has_migrated_from_chat_id()) { if (!channel->mgInfo) { - channel->flags |= MTPDchannel::flag_megagroup; + channel->flags |= MTPDchannel::Flag::f_megagroup; channel->flagsUpdated(); } ChatData *cfrom = App::chat(peerFromChat(f.vmigrated_from_chat_id)); @@ -273,7 +245,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt if (!h->isEmpty()) { h->clear(true); } - if (!hto->dialogs.isEmpty() && !h->dialogs.isEmpty()) { + if (hto->inChatList() && h->inChatList()) { App::removeDialog(h); } } @@ -317,6 +289,13 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt h->asChannelHistory()->unreadCountAll = f.vunread_count.v; } } + if (channel->isMegagroup()) { + if (f.has_pinned_msg_id()) { + channel->mgInfo->pinnedMsgId = f.vpinned_msg_id.v; + } else { + channel->mgInfo->pinnedMsgId = 0; + } + } channel->fullUpdated(); App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), f.vnotify_settings); @@ -344,12 +323,21 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestId req) { const MTPDuserFull &d(result.c_userFull()); App::feedUsers(MTP_vector(1, d.vuser), false); - App::feedPhoto(d.vprofile_photo); + if (d.has_profile_photo()) { + App::feedPhoto(d.vprofile_photo); + } App::feedUserLink(MTP_int(peerToUser(peer->id)), d.vlink.c_contacts_link().vmy_link, d.vlink.c_contacts_link().vforeign_link, false); - App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), d.vnotify_settings); + if (App::main()) { + App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), d.vnotify_settings); + } - peer->asUser()->setBotInfo(d.vbot_info); - peer->asUser()->blocked = mtpIsTrue(d.vblocked) ? UserIsBlocked : UserIsNotBlocked; + if (d.has_bot_info()) { + peer->asUser()->setBotInfo(d.vbot_info); + } else { + peer->asUser()->setBotInfoVersion(-1); + } + peer->asUser()->blocked = d.is_blocked() ? UserIsBlocked : UserIsNotBlocked; + peer->asUser()->about = d.has_about() ? qs(d.vabout) : QString(); if (req) { QMap::iterator i = _fullPeerRequests.find(peer); @@ -419,18 +407,18 @@ void ApiWrap::requestLastParticipants(ChannelData *peer, bool fromStart) { return; } } - mtpRequestId req = MTP::send(MTPchannels_GetParticipants(peer->inputChannel, MTP_channelParticipantsRecent(), MTP_int(fromStart ? 0 : peer->mgInfo->lastParticipants.size()), MTP_int(cMaxGroupCount())), rpcDone(&ApiWrap::lastParticipantsDone, peer), rpcFail(&ApiWrap::lastParticipantsFail, peer)); + mtpRequestId req = MTP::send(MTPchannels_GetParticipants(peer->inputChannel, MTP_channelParticipantsRecent(), MTP_int(fromStart ? 0 : peer->mgInfo->lastParticipants.size()), MTP_int(Global::ChatSizeMax())), rpcDone(&ApiWrap::lastParticipantsDone, peer), rpcFail(&ApiWrap::lastParticipantsFail, peer)); _participantsRequests.insert(peer, fromStart ? req : -req); } void ApiWrap::requestBots(ChannelData *peer) { if (!peer || !peer->isMegagroup() || _botsRequests.contains(peer)) return; - _botsRequests.insert(peer, MTP::send(MTPchannels_GetParticipants(peer->inputChannel, MTP_channelParticipantsBots(), MTP_int(0), MTP_int(cMaxGroupCount())), rpcDone(&ApiWrap::lastParticipantsDone, peer), rpcFail(&ApiWrap::lastParticipantsFail, peer))); + _botsRequests.insert(peer, MTP::send(MTPchannels_GetParticipants(peer->inputChannel, MTP_channelParticipantsBots(), MTP_int(0), MTP_int(Global::ChatSizeMax())), rpcDone(&ApiWrap::lastParticipantsDone, peer), rpcFail(&ApiWrap::lastParticipantsFail, peer))); } void ApiWrap::gotChat(PeerData *peer, const MTPmessages_Chats &result) { _peerRequests.remove(peer); - + if (result.type() == mtpc_messages_chats) { const QVector &v(result.c_messages_chats().vchats.c_vector().v); bool badVersion = false; @@ -524,7 +512,7 @@ void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelP UserData *u = App::user(userId); if (bots) { if (u->botInfo) { - peer->mgInfo->bots.insert(u, true); + peer->mgInfo->bots.insert(u); botStatus = 2;// (botStatus > 0/* || !i.key()->botInfo->readsAllHistory*/) ? 2 : 1; if (!u->botInfo->inited) { needBotsInfos = true; @@ -536,9 +524,9 @@ void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelP } else { if (peer->mgInfo->lastParticipants.indexOf(u) < 0) { peer->mgInfo->lastParticipants.push_back(u); - if (admin) peer->mgInfo->lastAdmins.insert(u, true); + if (admin) peer->mgInfo->lastAdmins.insert(u); if (u->botInfo) { - peer->mgInfo->bots.insert(u, true); + peer->mgInfo->bots.insert(u); if (peer->mgInfo->botStatus != 0 && peer->mgInfo->botStatus < 2) { peer->mgInfo->botStatus = 2; } @@ -645,15 +633,23 @@ void ApiWrap::kickParticipantDone(KickRequest kick, const MTPUpdates &result, mt int32 i = kick.first->asChannel()->mgInfo->lastParticipants.indexOf(kick.second); if (i >= 0) { kick.first->asChannel()->mgInfo->lastParticipants.removeAt(i); - kick.first->asChannel()->mgInfo->lastAdmins.remove(kick.second); } - kick.first->asChannel()->mgInfo->bots.remove(kick.second); if (kick.first->asChannel()->count > 1) { - kick.first->asChannel()->count--; + --kick.first->asChannel()->count; } else { kick.first->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; kick.first->asChannel()->mgInfo->lastParticipantsCount = 0; } + if (kick.first->asChannel()->mgInfo->lastAdmins.contains(kick.second)) { + kick.first->asChannel()->mgInfo->lastAdmins.remove(kick.second); + if (kick.first->asChannel()->adminsCount > 1) { + --kick.first->asChannel()->adminsCount; + } + } + kick.first->asChannel()->mgInfo->bots.remove(kick.second); + if (kick.first->asChannel()->mgInfo->bots.isEmpty() && kick.first->asChannel()->mgInfo->botStatus > 0) { + kick.first->asChannel()->mgInfo->botStatus = -1; + } } emit fullPeerUpdated(kick.first); } @@ -672,9 +668,9 @@ void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) { void ApiWrap::requestStickerSets() { for (QMap >::iterator i = _stickerSetRequests.begin(), j = i, e = _stickerSetRequests.end(); i != e; i = j) { + ++j; if (i.value().second) continue; - ++j; int32 wait = (j == e) ? 0 : 10; i.value().second = MTP::send(MTPmessages_GetStickerSet(MTP_inputStickerSetID(MTP_long(i.key()), MTP_long(i.value().first))), rpcDone(&ApiWrap::gotStickerSet, i.key()), rpcFail(&ApiWrap::gotStickerSetFail, i.key()), 0, wait); } @@ -682,15 +678,15 @@ void ApiWrap::requestStickerSets() { void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) { _stickerSetRequests.remove(setId); - + if (result.type() != mtpc_messages_stickerSet) return; const MTPDmessages_stickerSet &d(result.c_messages_stickerSet()); - + if (d.vset.type() != mtpc_stickerSet) return; const MTPDstickerSet &s(d.vset.c_stickerSet()); - StickerSets &sets(cRefStickerSets()); - StickerSets::iterator it = sets.find(setId); + Stickers::Sets &sets(Global::RefStickerSets()); + auto it = sets.find(setId); if (it == sets.cend()) return; it->access = s.vaccess_hash.v; @@ -700,7 +696,7 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) it->flags = s.vflags.v; const QVector &d_docs(d.vdocuments.c_vector().v); - StickerSets::iterator custom = sets.find(CustomStickerSetId); + auto custom = sets.find(Stickers::CustomSetId); StickerPack pack; pack.reserve(d_docs.size()); @@ -731,12 +727,32 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) ++i; } } + if (pack.isEmpty()) { - int32 removeIndex = cStickerSetsOrder().indexOf(setId); - if (removeIndex >= 0) cRefStickerSetsOrder().removeAt(removeIndex); + int removeIndex = Global::StickerSetsOrder().indexOf(setId); + if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); sets.erase(it); } else { it->stickers = pack; + it->emoji.clear(); + const QVector &v(d.vpacks.c_vector().v); + for (int32 i = 0, l = v.size(); i < l; ++i) { + if (v.at(i).type() != mtpc_stickerPack) continue; + + const MTPDstickerPack &pack(v.at(i).c_stickerPack()); + if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) { + const QVector &stickers(pack.vdocuments.c_vector().v); + StickerPack p; + p.reserve(stickers.size()); + for (int32 j = 0, c = stickers.size(); j < c; ++j) { + DocumentData *doc = App::document(stickers.at(j).v); + if (!doc || !doc->sticker()) continue; + + p.push_back(doc); + } + it->emoji.insert(e, p); + } + } } if (writeRecent) { @@ -887,10 +903,8 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs } for (QMap::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) { - HistoryItem *item = App::histories().addNewMessage(v->at(i.value()), NewMessageExisting); - if (item) { - item->initDimensions(); - Notify::historyItemResized(item); + if (HistoryItem *item = App::histories().addNewMessage(v->at(i.value()), NewMessageExisting)) { + item->setPendingInitDimensions(); } } @@ -902,8 +916,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs WebPageItems::const_iterator j = items.constFind(i.key()); if (j != items.cend()) { for (HistoryItemsMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) { - k.key()->initDimensions(); - Notify::historyItemResized(k.key()); + k.key()->setPendingInitDimensions(); } } } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 5daabcf74f..8845d90bd0 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -28,9 +28,8 @@ public: ApiWrap(QObject *parent); void init(); - void itemRemoved(HistoryItem *item); - - void requestReplyTo(HistoryReply *reply, ChannelData *channel, MsgId id); + typedef SharedCallback RequestMessageDataCallback; + void requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback *callback); void requestFullPeer(PeerData *peer); void requestPeer(PeerData *peer); @@ -59,35 +58,37 @@ signals: public slots: - void resolveReplyTo(); + void resolveMessageDatas(); void resolveWebPages(); void delayedRequestParticipantsCount(); private: - void gotReplyTo(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req); - struct ReplyToRequest { - ReplyToRequest() : req(0) { + void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req); + struct MessageDataRequest { + MessageDataRequest() : req(0) { } + typedef SharedCallback::Ptr CallbackPtr; + typedef QList Callbacks; mtpRequestId req; - QList replies; + Callbacks callbacks; }; - typedef QMap ReplyToRequests; - ReplyToRequests _replyToRequests; - typedef QMap ChannelReplyToRequests; - ChannelReplyToRequests _channelReplyToRequests; - SingleTimer _replyToTimer; + typedef QMap MessageDataRequests; + MessageDataRequests _messageDataRequests; + typedef QMap ChannelMessageDataRequests; + ChannelMessageDataRequests _channelMessageDataRequests; + SingleDelayedCall *_messageDataResolveDelayed; typedef QVector MessageIds; - MessageIds collectMessageIds(const ReplyToRequests &requests); - ReplyToRequests *replyToRequests(ChannelData *channel, bool onlyExisting = false); + MessageIds collectMessageIds(const MessageDataRequests &requests); + MessageDataRequests *messageDataRequests(ChannelData *channel, bool onlyExisting = false); void gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mtpRequestId req); void gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestId req); bool gotPeerFullFailed(PeerData *peer, const RPCError &err); typedef QMap PeerRequests; PeerRequests _fullPeerRequests; - + void gotChat(PeerData *peer, const MTPmessages_Chats &result); void gotUser(PeerData *peer, const MTPVector &result); void gotChats(const MTPmessages_Chats &result); diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 045f77865a..901fc71c88 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" @@ -25,14 +25,15 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "application.h" #include "fileuploader.h" #include "mainwidget.h" +#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) #include - +#endif #include "localstorage.h" #include "numbers.h" namespace { - bool quiting = false; + App::LaunchState _launchState = App::Launched; UserData *self = 0; @@ -46,32 +47,23 @@ namespace { UpdatedPeers updatedPeers; PhotosData photosData; - VideosData videosData; - AudiosData audiosData; DocumentsData documentsData; - typedef QHash ImageLinksData; - ImageLinksData imageLinksData; + typedef QHash LocationsData; + LocationsData locationsData; typedef QHash WebPagesData; WebPagesData webPagesData; - typedef QMap ReplyMarkups; - ReplyMarkups replyMarkups; - ReplyMarkup zeroMarkup(MTPDreplyKeyboardMarkup_flag_ZERO); - typedef QMap ChannelReplyMarkups; - ChannelReplyMarkups channelReplyMarkups; - PhotoItems photoItems; - VideoItems videoItems; - AudioItems audioItems; DocumentItems documentItems; WebPageItems webPageItems; SharedContactItems sharedContactItems; GifItems gifItems; - typedef QMap > RepliesTo; - RepliesTo repliesTo; + typedef OrderedSet DependentItemsSet; + typedef QMap DependentItems; + DependentItems dependentItems; Histories histories; @@ -113,9 +105,6 @@ namespace { typedef QHash LastPhotosMap; LastPhotosMap lastPhotosMap; - typedef QMap InlineResultLoaders; - InlineResultLoaders inlineResultLoaders; - style::color _msgServiceBg; style::color _msgServiceSelectBg; style::color _historyScrollBarColor; @@ -151,12 +140,12 @@ namespace App { return result; } - Application *app() { - return Application::app(); + AppClass *app() { + return AppClass::app(); } Window *wnd() { - return Application::wnd(); + return AppClass::wnd(); } MainWidget *main() { @@ -182,21 +171,19 @@ namespace App { return main() ? main()->api() : 0; } +namespace { bool loggedOut() { - Window *w(wnd()); if (cHasPasscode()) { cSetHasPasscode(false); } if (audioPlayer()) { audioPlayer()->stopAndClear(); } - if (w) { + if (Window *w = wnd()) { w->tempDirDelete(Local::ClearManagerAll); w->notifyClearFast(); w->setupIntro(true); } - MainWidget *m(main()); - if (m) m->destroyData(); MTP::authed(0); Local::reset(); @@ -207,13 +194,14 @@ namespace App { globalNotifyChatsPtr = UnknownNotifySettings; if (App::uploader()) App::uploader()->clear(); clearStorageImages(); - if (w) { + if (Window *w = wnd()) { w->getTitle()->updateBackButton(); w->updateTitleStatus(); w->getTitle()->resizeEvent(0); } return true; } +} // namespace void logOut() { if (MTP::started()) { @@ -329,6 +317,25 @@ namespace App { return lng_status_lastseen_date(lt_date, dOnline.date().toString(qsl("dd.MM.yy"))); } + namespace { + // we should get a full restriction in "{fulltype}: {reason}" format and we + // need to find a "-all" tag in {fulltype}, otherwise ignore this restriction + QString extractRestrictionReason(const QString &fullRestriction) { + int fullTypeEnd = fullRestriction.indexOf(':'); + if (fullTypeEnd <= 0) { + return QString(); + } + + // {fulltype} is in "{type}-{tag}-{tag}-{tag}" format + // if we find "all" tag we return the restriction string + QStringList typeTags = fullRestriction.mid(0, fullTypeEnd).split('-').mid(1); + if (typeTags.contains(qsl("all"))) { + return fullRestriction.midRef(fullTypeEnd + 1).trimmed().toString(); + } + return QString(); + } + } + bool onlineColorUse(UserData *user, int32 now) { if (isServiceUser(user->id) || user->botInfo) { return false; @@ -353,7 +360,7 @@ namespace App { for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { const MTPuser &user(*i); data = 0; - bool wasContact = false; + bool wasContact = false, minimal = false; const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty(); switch (user.type()) { @@ -375,19 +382,27 @@ namespace App { } break; case mtpc_user: { const MTPDuser &d(user.c_user()); + minimal = d.is_min(); PeerId peer(peerFromUser(d.vid.v)); data = App::user(peer); - data->flags = d.vflags.v; - if (d.is_self()) { - data->input = MTP_inputPeerSelf(); - data->inputUser = MTP_inputUserSelf(); - } else if (!d.has_access_hash()) { - data->input = MTP_inputPeerUser(d.vid, MTP_long((data->access == UserNoAccess) ? 0 : data->access)); - data->inputUser = MTP_inputUser(d.vid, MTP_long((data->access == UserNoAccess) ? 0 : data->access)); - } else { - data->input = MTP_inputPeerUser(d.vid, d.vaccess_hash); - data->inputUser = MTP_inputUser(d.vid, d.vaccess_hash); + if (!minimal) { + data->flags = d.vflags.v; + if (d.is_self()) { + data->input = MTP_inputPeerSelf(); + data->inputUser = MTP_inputUserSelf(); + } else if (!d.has_access_hash()) { + data->input = MTP_inputPeerUser(d.vid, MTP_long((data->access == UserNoAccess) ? 0 : data->access)); + data->inputUser = MTP_inputUser(d.vid, MTP_long((data->access == UserNoAccess) ? 0 : data->access)); + } else { + data->input = MTP_inputPeerUser(d.vid, d.vaccess_hash); + data->inputUser = MTP_inputUser(d.vid, d.vaccess_hash); + } + if (d.is_restricted()) { + data->setRestrictionReason(extractRestrictionReason(qs(d.vrestriction_reason))); + } else { + data->setRestrictionReason(QString()); + } } if (d.is_deleted()) { data->setPhone(QString()); @@ -396,10 +411,14 @@ namespace App { data->access = UserNoAccess; status = &emptyStatus; } else { - QString phone = d.has_phone() ? qs(d.vphone) : QString(); - QString fname = d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString(); - QString lname = d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString(); - QString uname = d.has_username() ? textOneLine(qs(d.vusername)) : QString(); + // apply first_name and last_name from minimal user only if we don't have + // local values for first name and last name already, otherwise skip + bool noLocalName = data->firstName.isEmpty() && data->lastName.isEmpty(); + QString fname = (!minimal || noLocalName) ? (d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString()) : data->firstName; + QString lname = (!minimal || noLocalName) ? (d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString()) : data->lastName; + + QString phone = minimal ? data->phone : (d.has_phone() ? qs(d.vphone) : QString()); + QString uname = minimal ? data->username : (d.has_username() ? textOneLine(qs(d.vusername)) : QString()); bool phoneChanged = (data->phone != phone); if (phoneChanged) data->setPhone(phone); @@ -408,11 +427,18 @@ namespace App { bool showPhone = !isServiceUser(data->id) && !d.is_self() && !d.is_contact() && !d.is_mutual_contact(); bool showPhoneChanged = !isServiceUser(data->id) && !d.is_self() && ((showPhone && data->contact) || (!showPhone && !data->contact)); + if (minimal) { + showPhoneChanged = false; + showPhone = !isServiceUser(data->id) && (data->id != peerFromUser(MTP::authedId())) && !data->contact; + } // see also Local::readPeer QString pname = (showPhoneChanged || phoneChanged || nameChanged) ? ((showPhone && !phone.isEmpty()) ? formatPhone(phone) : QString()) : data->nameOrPhone; + if (!minimal && d.is_self() && uname != data->username) { + SignalHandlers::setCrashAnnotation("Username", uname); + } data->setName(fname, lname, pname, uname); if (d.has_photo()) { data->setPhoto(d.vphoto); @@ -423,30 +449,38 @@ namespace App { status = d.has_status() ? &d.vstatus : &emptyStatus; } wasContact = (data->contact > 0); - if (d.has_bot_info_version()) { - data->setBotInfoVersion(d.vbot_info_version.v); - data->botInfo->readsAllHistory = d.is_bot_chat_history(); - data->botInfo->cantJoinGroups = d.is_bot_nochats(); - data->botInfo->inlinePlaceholder = d.has_bot_inline_placeholder() ? '_' + qs(d.vbot_inline_placeholder) : QString(); - } else { - data->setBotInfoVersion(-1); - } - data->contact = (d.is_contact() || d.is_mutual_contact()) ? 1 : (data->phone.isEmpty() ? -1 : 0); - if (data->contact == 1 && cReportSpamStatuses().value(data->id, dbiprsNoButton) != dbiprsNoButton) { - cRefReportSpamStatuses().insert(data->id, dbiprsNoButton); - Local::writeReportSpamStatuses(); - } - if (d.is_self() && ::self != data) { - ::self = data; - if (App::wnd()) App::wnd()->updateGlobalMenu(); + if (!minimal) { + if (d.has_bot_info_version()) { + data->setBotInfoVersion(d.vbot_info_version.v); + data->botInfo->readsAllHistory = d.is_bot_chat_history(); + data->botInfo->cantJoinGroups = d.is_bot_nochats(); + data->botInfo->inlinePlaceholder = d.has_bot_inline_placeholder() ? '_' + qs(d.vbot_inline_placeholder) : QString(); + } else { + data->setBotInfoVersion(-1); + } + data->contact = (d.is_contact() || d.is_mutual_contact()) ? 1 : (data->phone.isEmpty() ? -1 : 0); + if (data->contact == 1 && cReportSpamStatuses().value(data->id, dbiprsHidden) != dbiprsHidden) { + cRefReportSpamStatuses().insert(data->id, dbiprsHidden); + Local::writeReportSpamStatuses(); + } + if (d.is_self() && ::self != data) { + ::self = data; + if (App::wnd()) App::wnd()->updateGlobalMenu(); + } } } break; } if (!data) continue; - data->loaded = true; - if (status) switch (status->type()) { + if (minimal) { + if (data->loadedStatus == PeerData::NotLoaded) { + data->loadedStatus = PeerData::MinimalLoaded; + } + } else if (data->loadedStatus != PeerData::FullLoaded) { + data->loadedStatus = PeerData::FullLoaded; + } + if (status && !minimal) switch (status->type()) { case mtpc_userStatusEmpty: data->onlineTill = 0; break; case mtpc_userStatusRecently: if (data->onlineTill > -10) { // don't modify pseudo-online @@ -484,6 +518,7 @@ namespace App { for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { const MTPchat &chat(*i); data = 0; + bool minimal = false; switch (chat.type()) { case mtpc_chat: { const MTPDchat &d(chat.c_chat()); @@ -501,7 +536,7 @@ namespace App { const MTPDinputChannel &c(d.vmigrated_to.c_inputChannel()); ChannelData *channel = App::channel(peerFromChannel(c.vchannel_id)); if (!channel->mgInfo) { - channel->flags |= MTPDchannel::flag_megagroup; + channel->flags |= MTPDchannel::Flag::f_megagroup; channel->flagsUpdated(); } if (!channel->access) { @@ -520,7 +555,7 @@ namespace App { if (!h->isEmpty()) { h->clear(true); } - if (!hto->dialogs.isEmpty() && !h->dialogs.isEmpty()) { + if (hto->inChatList() && h->inChatList()) { App::removeDialog(h); } } @@ -532,7 +567,7 @@ namespace App { } } - if (!(cdata->flags & MTPDchat::flag_admins_enabled) && (d.vflags.v & MTPDchat::flag_admins_enabled)) { + if (!(cdata->flags & MTPDchat::Flag::f_admins_enabled) && (d.vflags.v & MTPDchat::Flag::f_admins_enabled)) { cdata->invalidateParticipants(); } cdata->flags = d.vflags.v; @@ -564,24 +599,41 @@ namespace App { const MTPDchannel &d(chat.c_channel()); PeerId peer(peerFromChannel(d.vid.v)); - data = App::channel(peer); - data->input = MTP_inputPeerChannel(d.vid, d.vaccess_hash); + minimal = d.is_min(); + if (minimal) { + data = App::channelLoaded(peer); + if (!data) { + continue; // minimal is not loaded, need to make getDifference + } + } else { + data = App::channel(peer); + data->input = MTP_inputPeerChannel(d.vid, d.has_access_hash() ? d.vaccess_hash : MTP_long(0)); + } ChannelData *cdata = data->asChannel(); - cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); - + if (minimal) { + int32 mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy; + cdata->flags = (cdata->flags & ~mask) | (d.vflags.v & mask); + } else { + cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); + cdata->access = d.vaccess_hash.v; + cdata->date = d.vdate.v; + cdata->flags = d.vflags.v; + if (cdata->version < d.vversion.v) { + cdata->version = d.vversion.v; + } + if (d.is_restricted()) { + cdata->setRestrictionReason(extractRestrictionReason(qs(d.vrestriction_reason))); + } else { + cdata->setRestrictionReason(QString()); + } + } QString uname = d.has_username() ? textOneLine(qs(d.vusername)) : QString(); cdata->setName(qs(d.vtitle), uname); - cdata->access = d.vaccess_hash.v; - cdata->date = d.vdate.v; - cdata->flags = d.vflags.v; cdata->isForbidden = false; cdata->flagsUpdated(); cdata->setPhoto(d.vphoto); - if (cdata->version < d.vversion.v) { - cdata->version = d.vversion.v; - } } break; case mtpc_channelForbidden: { const MTPDchannelForbidden &d(chat.c_channelForbidden()); @@ -604,7 +656,13 @@ namespace App { } if (!data) continue; - data->loaded = true; + if (minimal) { + if (data->loadedStatus == PeerData::NotLoaded) { + data->loadedStatus = PeerData::MinimalLoaded; + } + } else if (data->loadedStatus != PeerData::FullLoaded) { + data->loadedStatus = PeerData::FullLoaded; + } if (App::main()) { if (emitPeerUpdated) { App::main()->peerUpdated(data); @@ -636,7 +694,7 @@ namespace App { int32 pversion = chat->participants.isEmpty() ? 1 : (chat->participants.begin().value() + 1); chat->invitedByMe = ChatData::InvitedByMe(); chat->admins = ChatData::Admins(); - chat->flags &= ~MTPDchat::flag_admin; + chat->flags &= ~MTPDchat::Flag::f_admin; for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { int32 uid = 0, inviter = 0; switch (i->type()) { @@ -662,12 +720,12 @@ namespace App { if (user) { chat->participants[user] = pversion; if (inviter == MTP::authedId()) { - chat->invitedByMe[user] = true; + chat->invitedByMe.insert(user); } if (i->type() == mtpc_chatParticipantAdmin) { - chat->admins[user] = true; + chat->admins.insert(user); if (user->isSelf()) { - chat->flags |= MTPDchat::flag_admin; + chat->flags |= MTPDchat::Flag::f_admin; } } } else { @@ -734,7 +792,7 @@ namespace App { } else if (chat->participants.find(user) == chat->participants.end()) { chat->participants[user] = (chat->participants.isEmpty() ? 1 : chat->participants.begin().value()); if (d.vinviter_id.v == MTP::authedId()) { - chat->invitedByMe[user] = true; + chat->invitedByMe.insert(user); } else { chat->invitedByMe.remove(user); } @@ -787,7 +845,7 @@ namespace App { chat->invitedByMe.remove(user); chat->admins.remove(user); if (user->isSelf()) { - chat->flags &= ~MTPDchat::flag_admin; + chat->flags &= ~MTPDchat::Flag::f_admin; } History *h = App::historyLoaded(chat->id); @@ -834,13 +892,12 @@ namespace App { } chat->version = d.vversion.v; if (mtpIsTrue(d.venabled)) { - chat->flags |= MTPDchat::flag_admins_enabled; + chat->flags |= MTPDchat::Flag::f_admins_enabled; if (!badVersion) { chat->invalidateParticipants(); } } else { - chat->flags &= ~MTPDchat::flag_admins_enabled; - chat->flags &= ~MTPDchat::flag_admin; + chat->flags &= ~MTPDchat::Flag::f_admins_enabled; } if (emitPeerUpdated) { App::main()->peerUpdated(chat); @@ -869,16 +926,16 @@ namespace App { if (user) { if (mtpIsTrue(d.vis_admin)) { if (user->isSelf()) { - chat->flags |= MTPDchat::flag_admin; + chat->flags |= MTPDchat::Flag::f_admin; } if (chat->noParticipantInfo()) { App::api()->requestFullPeer(chat); } else { - chat->admins.insert(user, true); + chat->admins.insert(user); } } else { if (user->isSelf()) { - chat->flags &= ~MTPDchat::flag_admin; + chat->flags &= ~MTPDchat::Flag::f_admin; } chat->admins.remove(user); } @@ -902,12 +959,9 @@ namespace App { } if (HistoryItem *existing = App::histItemById(peerToChannel(peerId), m.vid.v)) { existing->setText(qs(m.vmessage), m.has_entities() ? entitiesFromMTP(m.ventities.c_vector().v) : EntitiesInText()); - existing->initDimensions(); - Notify::historyItemResized(existing); - - existing->updateMedia(m.has_media() ? (&m.vmedia) : 0, true); - existing->addToOverview(AddToOverviewNew); + existing->updateMedia(m.has_media() ? (&m.vmedia) : nullptr); existing->setViewsCount(m.has_views() ? m.vviews.v : -1); + existing->addToOverview(AddToOverviewNew); if (!existing->detached()) { App::checkSavedGif(existing); @@ -919,21 +973,33 @@ namespace App { return false; } + void updateEditedMessage(const MTPDmessage &m) { + PeerId peerId = peerFromMTP(m.vto_id); + if (m.has_from_id() && peerToUser(peerId) == MTP::authedId()) { + peerId = peerFromUser(m.vfrom_id); + } + if (HistoryItem *existing = App::histItemById(peerToChannel(peerId), m.vid.v)) { + existing->applyEdition(m); + } + } + void addSavedGif(DocumentData *doc) { SavedGifs &saved(cRefSavedGifs()); int32 index = saved.indexOf(doc); if (index) { if (index > 0) saved.remove(index); saved.push_front(doc); - if (saved.size() > cSavedGifsLimit()) saved.pop_back(); + if (saved.size() > Global::SavedGifsLimit()) saved.pop_back(); Local::writeSavedGifs(); if (App::main()) emit App::main()->savedGifsUpdated(); + cSetLastSavedGifsUpdate(0); + App::main()->updateStickers(); } } void checkSavedGif(HistoryItem *item) { - if (!item->toHistoryForwarded() && item->out()) { + if (!item->Has() && (item->out() || item->history()->peer == App::self())) { if (HistoryMedia *media = item->getMedia()) { if (DocumentData *doc = media->getDocument()) { if (doc->isGifv()) { @@ -1054,7 +1120,6 @@ namespace App { } void feedWereDeleted(ChannelId channelId, const QVector &msgsIds) { - bool resized = false; MsgsData *data = fetchMsgsData(channelId, false); if (!data) return; @@ -1065,9 +1130,6 @@ namespace App { MsgsData::const_iterator j = data->constFind(i->v); if (j != data->cend()) { History *h = (*j)->history(); - if (App::main() && (h->peer == App::main()->peer() || (App::main()->peer() && h->peer->migrateTo() == App::main()->peer())) && !(*j)->detached()) { - resized = true; - } (*j)->destroy(); if (!h->lastMsg) historiesToCheck.insert(h, true); } else { @@ -1079,9 +1141,6 @@ namespace App { } } } - if (resized) { - Notify::historyItemsResized(); - } if (main()) { for (QMap::const_iterator i = historiesToCheck.cbegin(), e = historiesToCheck.cend(); i != e; ++i) { main()->checkPeerHistory(i.key()->peer); @@ -1120,8 +1179,8 @@ namespace App { switch (myLink.type()) { case mtpc_contactLinkContact: user->contact = 1; - if (user->contact == 1 && cReportSpamStatuses().value(user->id, dbiprsNoButton) != dbiprsNoButton) { - cRefReportSpamStatuses().insert(user->id, dbiprsNoButton); + if (user->contact == 1 && cReportSpamStatuses().value(user->id, dbiprsHidden) != dbiprsHidden) { + cRefReportSpamStatuses().insert(user->id, dbiprsHidden); Local::writeReportSpamStatuses(); } break; @@ -1286,26 +1345,6 @@ namespace App { return App::photoSet(photo.vid.v, convert, 0, 0, ImagePtr(), ImagePtr(), ImagePtr()); } - VideoData *feedVideo(const MTPDvideo &video, VideoData *convert) { - return App::videoSet(video.vid.v, convert, video.vaccess_hash.v, video.vdate.v, video.vduration.v, video.vw.v, video.vh.v, App::image(video.vthumb), video.vdc_id.v, video.vsize.v); - } - - AudioData *feedAudio(const MTPaudio &audio, AudioData *convert) { - switch (audio.type()) { - case mtpc_audio: { - return feedAudio(audio.c_audio(), convert); - } break; - case mtpc_audioEmpty: { - return App::audioSet(audio.c_audioEmpty().vid.v, convert, 0, 0, QString(), 0, 0, 0); - } break; - } - return App::audio(0); - } - - AudioData *feedAudio(const MTPDaudio &audio, AudioData *convert) { - return App::audioSet(audio.vid.v, convert, audio.vaccess_hash.v, audio.vdate.v, qs(audio.vmime_type), audio.vduration.v, audio.vdc_id.v, audio.vsize.v); - } - DocumentData *feedDocument(const MTPdocument &document, const QPixmap &thumb) { switch (document.type()) { case mtpc_document: { @@ -1354,41 +1393,16 @@ namespace App { return 0; } - PeerData *peerLoaded(const PeerId &peer) { - PeersData::const_iterator i = peersData.constFind(peer); - return (i != peersData.cend()) ? i.value() : 0; - } - - UserData *userLoaded(const PeerId &id) { - PeerData *peer = peerLoaded(id); - return (peer && peer->loaded) ? peer->asUser() : 0; - } - ChatData *chatLoaded(const PeerId &id) { - PeerData *peer = peerLoaded(id); - return (peer && peer->loaded) ? peer->asChat() : 0; - } - ChannelData *channelLoaded(const PeerId &id) { - PeerData *peer = peerLoaded(id); - return (peer && peer->loaded) ? peer->asChannel() : 0; - } - UserData *userLoaded(int32 user_id) { - return userLoaded(peerFromUser(user_id)); - } - ChatData *chatLoaded(int32 chat_id) { - return chatLoaded(peerFromChat(chat_id)); - } - ChannelData *channelLoaded(int32 channel_id) { - return channelLoaded(peerFromChannel(channel_id)); - } - UserData *curUser() { return user(MTP::authedId()); } - PeerData *peer(const PeerId &id) { - PeersData::const_iterator i = peersData.constFind(id); + PeerData *peer(const PeerId &id, PeerData::LoadedStatus restriction) { + if (!id) return nullptr; + + auto i = peersData.constFind(id); if (i == peersData.cend()) { - PeerData *newData = 0; + PeerData *newData = nullptr; if (peerIsUser(id)) { newData = new UserData(id); } else if (peerIsChat(id)) { @@ -1396,45 +1410,38 @@ namespace App { } else if (peerIsChannel(id)) { newData = new ChannelData(id); } - if (!newData) return 0; + t_assert(newData != nullptr); newData->input = MTPinputPeer(MTP_inputPeerEmpty()); i = peersData.insert(id, newData); } + switch (restriction) { + case PeerData::MinimalLoaded: { + if (i.value()->loadedStatus == PeerData::NotLoaded) { + return nullptr; + } + } break; + case PeerData::FullLoaded: { + if (i.value()->loadedStatus != PeerData::FullLoaded) { + return nullptr; + } + } break; + } return i.value(); } - UserData *user(const PeerId &id) { - return peer(id)->asUser(); - } - ChatData *chat(const PeerId &id) { - return peer(id)->asChat(); - } - ChannelData *channel(const PeerId &id) { - return peer(id)->asChannel(); - } - UserData *user(int32 user_id) { - return user(peerFromUser(user_id)); - } - ChatData *chat(int32 chat_id) { - return chat(peerFromChat(chat_id)); - } - ChannelData *channel(int32 channel_id) { - return channel(peerFromChannel(channel_id)); - } - UserData *self() { return ::self; } PeerData *peerByName(const QString &username) { QString uname(username.trimmed()); - for (PeersData::const_iterator i = peersData.cbegin(), e = peersData.cend(); i != e; ++i) { - if (!i.value()->userName().compare(uname, Qt::CaseInsensitive)) { - return i.value()->asUser(); + for_const (PeerData *peer, peersData) { + if (!peer->userName().compare(uname, Qt::CaseInsensitive)) { + return peer; } } - return 0; + return nullptr; } void updateImage(ImagePtr &old, ImagePtr now) { @@ -1511,110 +1518,6 @@ namespace App { return result; } - VideoData *video(const VideoId &video) { - VideosData::const_iterator i = ::videosData.constFind(video); - if (i == ::videosData.cend()) { - i = ::videosData.insert(video, new VideoData(video)); - } - return i.value(); - } - - VideoData *videoSet(const VideoId &video, VideoData *convert, const uint64 &access, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size) { - if (convert) { - if (convert->id != video) { - VideosData::iterator i = ::videosData.find(convert->id); - if (i != ::videosData.cend() && i.value() == convert) { - ::videosData.erase(i); - } - convert->id = video; - convert->status = FileReady; - } - if (date) { - convert->access = access; - convert->date = date; - updateImage(convert->thumb, thumb); - convert->duration = duration; - convert->w = w; - convert->h = h; - convert->dc = dc; - convert->size = size; - } - } - VideosData::const_iterator i = ::videosData.constFind(video); - VideoData *result; - if (i == ::videosData.cend()) { - if (convert) { - result = convert; - } else { - result = new VideoData(video, access, date, duration, w, h, thumb, dc, size); - } - ::videosData.insert(video, result); - } else { - result = i.value(); - if (result != convert && date) { - result->access = access; - result->date = date; - result->duration = duration; - result->w = w; - result->h = h; - updateImage(result->thumb, thumb); - result->dc = dc; - result->size = size; - } - } - return result; - } - - AudioData *audio(const AudioId &audio) { - AudiosData::const_iterator i = ::audiosData.constFind(audio); - if (i == ::audiosData.cend()) { - i = ::audiosData.insert(audio, new AudioData(audio)); - } - return i.value(); - } - - AudioData *audioSet(const AudioId &audio, AudioData *convert, const uint64 &access, int32 date, const QString &mime, int32 duration, int32 dc, int32 size) { - if (convert) { - if (convert->id != audio) { - AudiosData::iterator i = ::audiosData.find(convert->id); - if (i != ::audiosData.cend() && i.value() == convert) { - ::audiosData.erase(i); - } - convert->id = audio; - convert->status = FileReady; - } - if (date) { - convert->access = access; - convert->date = date; - convert->mime = mime; - convert->duration = duration; - convert->dc = dc; - convert->size = size; - } - } - AudiosData::const_iterator i = ::audiosData.constFind(audio); - AudioData *result; - if (i == ::audiosData.cend()) { - if (convert) { - result = convert; - } else { - result = new AudioData(audio, access, date, mime, duration, dc, size); - } - ::audiosData.insert(audio, result); - } else { - result = i.value(); - if (result != convert && date) { - result->access = access; - result->date = date; - result->mime = mime; - result->duration = duration; - result->dc = dc; - result->size = size; - } - } - return result; - } - DocumentData *document(const DocumentId &document) { DocumentsData::const_iterator i = ::documentsData.constFind(document); if (i == ::documentsData.cend()) { @@ -1631,10 +1534,15 @@ namespace App { if (i != ::documentsData.cend() && i.value() == convert) { ::documentsData.erase(i); } - Local::copyStickerImage(mediaKey(DocumentFileLocation, convert->dc, convert->id), mediaKey(DocumentFileLocation, dc, document)); + + // inline bot sent gifs caching + if (!convert->voice() && !convert->isVideo()) { + Local::copyStickerImage(mediaKey(DocumentFileLocation, convert->dc, convert->id), mediaKey(DocumentFileLocation, dc, document)); + } + convert->id = document; convert->status = FileReady; - sentSticker = !!convert->sticker(); + sentSticker = (convert->sticker() != 0); } if (date) { convert->access = access; @@ -1642,7 +1550,7 @@ namespace App { convert->setattributes(attributes); convert->mime = mime; if (!thumb->isNull() && (convert->thumb->isNull() || convert->thumb->width() < thumb->width() || convert->thumb->height() < thumb->height())) { - convert->thumb = thumb; + updateImage(convert->thumb, thumb); } convert->dc = dc; convert->size = size; @@ -1658,7 +1566,7 @@ namespace App { const FileLocation &loc(convert->location(true)); if (!loc.isEmpty()) { - Local::writeFileLocation(mediaKey(DocumentFileLocation, convert->dc, convert->id), loc); + Local::writeFileLocation(convert->mediaKey(), loc); } } DocumentsData::const_iterator i = ::documentsData.constFind(document); @@ -1669,7 +1577,9 @@ namespace App { } else { result = new DocumentData(document, access, date, attributes, mime, thumb, dc, size); result->recountIsImage(); - if (result->sticker()) result->sticker()->loc = thumbLocation; + if (result->sticker()) { + result->sticker()->loc = thumbLocation; + } } ::documentsData.insert(document, result); } else { @@ -1690,7 +1600,9 @@ namespace App { } } } - if (sentSticker && App::main()) App::main()->incrementSticker(result); + if (sentSticker && App::main()) { + App::main()->incrementSticker(result); + } return result; } @@ -1762,43 +1674,24 @@ namespace App { return result; } - ImageLinkData *imageLink(const QString &imageLink) { - ImageLinksData::const_iterator i = imageLinksData.constFind(imageLink); - if (i == imageLinksData.cend()) { - i = imageLinksData.insert(imageLink, new ImageLinkData(imageLink)); + LocationData *location(const LocationCoords &coords) { + LocationsData::const_iterator i = locationsData.constFind(coords); + if (i == locationsData.cend()) { + i = locationsData.insert(coords, new LocationData(coords)); } return i.value(); } - ImageLinkData *imageLinkSet(const QString &imageLink, ImageLinkType type, const QString &url) { - ImageLinksData::const_iterator i = imageLinksData.constFind(imageLink); - ImageLinkData *result; - if (i == imageLinksData.cend()) { - result = new ImageLinkData(imageLink); - imageLinksData.insert(imageLink, result); - result->type = type; - } else { - result = i.value(); - } - return result; - } - void forgetMedia() { lastPhotos.clear(); lastPhotosMap.clear(); for (PhotosData::const_iterator i = ::photosData.cbegin(), e = ::photosData.cend(); i != e; ++i) { i.value()->forget(); } - for (VideosData::const_iterator i = ::videosData.cbegin(), e = ::videosData.cend(); i != e; ++i) { - i.value()->forget(); - } - for (AudiosData::const_iterator i = ::audiosData.cbegin(), e = ::audiosData.cend(); i != e; ++i) { - i.value()->forget(); - } for (DocumentsData::const_iterator i = ::documentsData.cbegin(), e = ::documentsData.cend(); i != e; ++i) { i.value()->forget(); } - for (ImageLinksData::const_iterator i = imageLinksData.cbegin(), e = imageLinksData.cend(); i != e; ++i) { + for (LocationsData::const_iterator i = ::locationsData.cbegin(), e = ::locationsData.cend(); i != e; ++i) { i.value()->thumb->forget(); } } @@ -1887,38 +1780,53 @@ namespace App { MsgsData *data = fetchMsgsData(item->channelId(), false); if (!data) return; - MsgsData::iterator i = data->find(item->id); + auto i = data->find(item->id); if (i != data->cend()) { if (i.value() == item) { data->erase(i); } } historyItemDetached(item); - RepliesTo::iterator j = ::repliesTo.find(item); - if (j != ::repliesTo.cend()) { - for (QMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) { - k.key()->replyToReplaced(item, 0); + auto j = ::dependentItems.find(item); + if (j != ::dependentItems.cend()) { + DependentItemsSet items; + std::swap(items, j.value()); + ::dependentItems.erase(j); + + for_const (HistoryItem *dependent, items) { + dependent->dependencyItemRemoved(item); } - ::repliesTo.erase(j); } - if (App::main() && !App::quiting()) { + if (App::main() && !App::quitting()) { App::main()->itemRemoved(item); } } - void historyClearMsgs() { - ::repliesTo.clear(); - - QVector toDelete; - for (MsgsData::const_iterator i = msgsData.cbegin(), e = msgsData.cend(); i != e; ++i) { - if ((*i)->detached()) { - toDelete.push_back(*i); + void historyUpdateDependent(HistoryItem *item) { + DependentItems::iterator j = ::dependentItems.find(item); + if (j != ::dependentItems.cend()) { + for_const (HistoryItem *dependent, j.value()) { + dependent->updateDependencyItem(); } } - for (ChannelMsgsData::const_iterator j = channelMsgsData.cbegin(), end = channelMsgsData.cend(); j != end; ++j) { - for (MsgsData::const_iterator i = j->cbegin(), e = j->cend(); i != e; ++i) { - if ((*i)->detached()) { - toDelete.push_back(*i); + if (App::main()) { + App::main()->itemEdited(item); + } + } + + void historyClearMsgs() { + ::dependentItems.clear(); + + QVector toDelete; + for_const (HistoryItem *item, msgsData) { + if (item->detached()) { + toDelete.push_back(item); + } + } + for_const (const MsgsData &chMsgsData, channelMsgsData) { + for_const (HistoryItem *item, chMsgsData) { + if (item->detached()) { + toDelete.push_back(item); } } } @@ -1929,8 +1837,6 @@ namespace App { } ::hoveredItem = ::pressedItem = ::hoveredLinkItem = ::pressedLinkItem = ::contextItem = 0; - replyMarkups.clear(); - channelReplyMarkups.clear(); } void historyClearItems() { @@ -1940,41 +1846,35 @@ namespace App { updatedPeers.clear(); cSetSavedPeers(SavedPeers()); cSetSavedPeersByTime(SavedPeersByTime()); - for (PeersData::const_iterator i = peersData.cbegin(), e = peersData.cend(); i != e; ++i) { - delete *i; + cSetRecentInlineBots(RecentInlineBots()); + for_const (PeerData *peer, peersData) { + delete peer; } peersData.clear(); - for (PhotosData::const_iterator i = ::photosData.cbegin(), e = ::photosData.cend(); i != e; ++i) { - delete *i; + for_const (PhotoData *photo, ::photosData) { + delete photo; } ::photosData.clear(); - for (VideosData::const_iterator i = ::videosData.cbegin(), e = ::videosData.cend(); i != e; ++i) { - delete *i; - } - ::videosData.clear(); - for (AudiosData::const_iterator i = ::audiosData.cbegin(), e = ::audiosData.cend(); i != e; ++i) { - delete *i; - } - ::audiosData.clear(); - for (DocumentsData::const_iterator i = ::documentsData.cbegin(), e = ::documentsData.cend(); i != e; ++i) { - delete *i; + for_const (DocumentData *document, ::documentsData) { + delete document; } ::documentsData.clear(); - for (WebPagesData::const_iterator i = webPagesData.cbegin(), e = webPagesData.cend(); i != e; ++i) { - delete *i; + for_const (WebPageData *webpage, webPagesData) { + delete webpage; } webPagesData.clear(); if (api()) api()->clearWebPageRequests(); cSetRecentStickers(RecentStickerPack()); - cSetStickerSets(StickerSets()); - cSetStickerSetsOrder(StickerSetsOrder()); - cSetLastStickersUpdate(0); + Global::SetStickerSets(Stickers::Sets()); + Global::SetStickerSetsOrder(Stickers::Order()); + Global::SetLastStickersUpdate(0); cSetSavedGifs(SavedGifs()); cSetLastSavedGifsUpdate(0); cSetReportSpamStatuses(ReportSpamStatuses()); + cSetAutoDownloadPhoto(0); + cSetAutoDownloadAudio(0); + cSetAutoDownloadGif(0); ::photoItems.clear(); - ::videoItems.clear(); - ::audioItems.clear(); ::documentItems.clear(); ::webPageItems.clear(); ::sharedContactItems.clear(); @@ -1985,16 +1885,16 @@ namespace App { if (App::wnd()) App::wnd()->updateGlobalMenu(); } - void historyRegReply(HistoryReply *reply, HistoryItem *to) { - ::repliesTo[to].insert(reply, true); + void historyRegDependency(HistoryItem *dependent, HistoryItem *dependency) { + ::dependentItems[dependency].insert(dependent); } - void historyUnregReply(HistoryReply *reply, HistoryItem *to) { - RepliesTo::iterator i = ::repliesTo.find(to); - if (i != ::repliesTo.cend()) { - i.value().remove(reply); + void historyUnregDependency(HistoryItem *dependent, HistoryItem *dependency) { + auto i = ::dependentItems.find(dependency); + if (i != ::dependentItems.cend()) { + i.value().remove(dependent); if (i.value().isEmpty()) { - ::repliesTo.erase(i); + ::dependentItems.erase(i); } } } @@ -2104,6 +2004,7 @@ namespace App { ::cornersMask[i]->setDevicePixelRatio(cRetinaFactor()); } prepareCorners(BlackCorners, st::msgRadius, st::black); + prepareCorners(WhiteCorners, st::msgRadius, st::white); prepareCorners(ServiceCorners, st::msgRadius, st::msgServiceBg); prepareCorners(ServiceSelectedCorners, st::msgRadius, st::msgServiceSelectBg); prepareCorners(SelectedOverlayCorners, st::msgRadius, st::msgSelectOverlay); @@ -2132,8 +2033,8 @@ namespace App { } void clearHistories() { - textlnkOver(TextLinkPtr()); - textlnkDown(TextLinkPtr()); + ClickHandler::clearActive(); + ClickHandler::unpressed(); histories().clear(); @@ -2271,23 +2172,22 @@ namespace App { } void quit() { - if (quiting()) return; + if (quitting()) return; + setLaunchState(QuitRequested); - setQuiting(); - if (wnd()) { - wnd()->quit(); - } - if (app()) { - app()->quit(); - } + Application::quit(); } - bool quiting() { - return ::quiting; + bool quitting() { + return _launchState != Launched; } - void setQuiting() { - ::quiting = true; + LaunchState launchState() { + return _launchState; + } + + void setLaunchState(LaunchState state) { + _launchState = state; } QImage readImage(QByteArray data, QByteArray *format, bool opaque, bool *animated) { @@ -2375,38 +2275,6 @@ namespace App { return ::photosData; } - void regVideoItem(VideoData *data, HistoryItem *item) { - ::videoItems[data].insert(item, NullType()); - } - - void unregVideoItem(VideoData *data, HistoryItem *item) { - ::videoItems[data].remove(item); - } - - const VideoItems &videoItems() { - return ::videoItems; - } - - const VideosData &videosData() { - return ::videosData; - } - - void regAudioItem(AudioData *data, HistoryItem *item) { - ::audioItems[data].insert(item, NullType()); - } - - void unregAudioItem(AudioData*data, HistoryItem *item) { - ::audioItems[data].remove(item); - } - - const AudioItems &audioItems() { - return ::audioItems; - } - - const AudiosData &audiosData() { - return ::audiosData; - } - void regDocumentItem(DocumentData *data, HistoryItem *item) { ::documentItems[data].insert(item, NullType()); } @@ -2468,7 +2336,7 @@ namespace App { QString phoneFromSharedContact(int32 userId) { SharedContactItems::const_iterator i = ::sharedContactItems.constFind(userId); - if (i != ::sharedContactItems.cend()) { + if (i != ::sharedContactItems.cend() && !i->isEmpty()) { HistoryMedia *media = i->cbegin().key()->getMedia(); if (media && media->type() == MediaTypeContact) { return static_cast(media)->phone(); @@ -2505,125 +2373,36 @@ namespace App { if (changeInMin) App::main()->updateMutedIn(changeInMin); } - void regInlineResultLoader(FileLoader *loader, InlineResult *result) { - ::inlineResultLoaders.insert(loader, result); - } - - void unregInlineResultLoader(FileLoader *loader) { - ::inlineResultLoaders.remove(loader); - } - - InlineResult *inlineResultFromLoader(FileLoader *loader) { - InlineResultLoaders::const_iterator i = ::inlineResultLoaders.find(loader); - return (i == ::inlineResultLoaders.cend()) ? 0 : i.value(); - } - - inline void insertReplyMarkup(ChannelId channelId, MsgId msgId, const ReplyMarkup &markup) { - if (channelId == NoChannel) { - replyMarkups.insert(msgId, markup); - } else { - channelReplyMarkups[channelId].insert(msgId, markup); - } - } - - void feedReplyMarkup(ChannelId channelId, MsgId msgId, const MTPReplyMarkup &markup) { - ReplyMarkup data; - ReplyMarkup::Commands &commands(data.commands); - switch (markup.type()) { - case mtpc_replyKeyboardMarkup: { - const MTPDreplyKeyboardMarkup &d(markup.c_replyKeyboardMarkup()); - data.flags = d.vflags.v; - - const QVector &v(d.vrows.c_vector().v); - if (!v.isEmpty()) { - commands.reserve(v.size()); - for (int32 i = 0, l = v.size(); i < l; ++i) { - switch (v.at(i).type()) { - case mtpc_keyboardButtonRow: { - const MTPDkeyboardButtonRow &r(v.at(i).c_keyboardButtonRow()); - const QVector &b(r.vbuttons.c_vector().v); - if (!b.isEmpty()) { - QList btns; - btns.reserve(b.size()); - for (int32 j = 0, s = b.size(); j < s; ++j) { - switch (b.at(j).type()) { - case mtpc_keyboardButton: { - btns.push_back(qs(b.at(j).c_keyboardButton().vtext)); - } break; - } - } - if (!btns.isEmpty()) commands.push_back(btns); - } - } break; - } - } - if (!commands.isEmpty()) { - insertReplyMarkup(channelId, msgId, data); - } - } - } break; - - case mtpc_replyKeyboardHide: { - const MTPDreplyKeyboardHide &d(markup.c_replyKeyboardHide()); - if (d.vflags.v) { - insertReplyMarkup(channelId, msgId, ReplyMarkup(d.vflags.v | MTPDreplyKeyboardMarkup_flag_ZERO)); - } - } break; - - case mtpc_replyKeyboardForceReply: { - const MTPDreplyKeyboardForceReply &d(markup.c_replyKeyboardForceReply()); - insertReplyMarkup(channelId, msgId, ReplyMarkup(d.vflags.v | MTPDreplyKeyboardMarkup_flag_FORCE_REPLY)); - } break; - } - } - - void clearReplyMarkup(ChannelId channelId, MsgId msgId) { - if (channelId == NoChannel) { - replyMarkups.remove(msgId); - } else { - ChannelReplyMarkups::iterator i = channelReplyMarkups.find(channelId); - if (i != channelReplyMarkups.cend()) { - i->remove(msgId); - if (i->isEmpty()) { - channelReplyMarkups.erase(i); - } - } - } - } - - inline const ReplyMarkup &replyMarkup(const ReplyMarkups &markups, MsgId msgId) { - ReplyMarkups::const_iterator i = markups.constFind(msgId); - if (i == markups.cend()) return zeroMarkup; - return i.value(); - } - const ReplyMarkup &replyMarkup(ChannelId channelId, MsgId msgId) { - if (channelId == NoChannel) { - return replyMarkup(replyMarkups, msgId); - } - ChannelReplyMarkups::const_iterator j = channelReplyMarkups.constFind(channelId); - if (j == channelReplyMarkups.cend()) return zeroMarkup; - return replyMarkup(*j, msgId); - } - void setProxySettings(QNetworkAccessManager &manager) { +#ifndef TDESKTOP_DISABLE_NETWORK_PROXY manager.setProxy(getHttpProxySettings()); +#endif } +#ifndef TDESKTOP_DISABLE_NETWORK_PROXY QNetworkProxy getHttpProxySettings() { - if (cConnectionType() == dbictHttpProxy) { - const ConnectionProxy &p(cConnectionProxy()); - return QNetworkProxy(QNetworkProxy::HttpProxy, p.host, p.port, p.user, p.password); + const ConnectionProxy *proxy = 0; + if (Global::started()) { + proxy = (cConnectionType() == dbictHttpProxy) ? (&cConnectionProxy()) : 0; + } else { + proxy = Sandbox::PreLaunchProxy().host.isEmpty() ? 0 : (&Sandbox::PreLaunchProxy()); + } + if (proxy) { + return QNetworkProxy(QNetworkProxy::HttpProxy, proxy->host, proxy->port, proxy->user, proxy->password); } return QNetworkProxy(QNetworkProxy::DefaultProxy); } +#endif void setProxySettings(QTcpSocket &socket) { +#ifndef TDESKTOP_DISABLE_NETWORK_PROXY if (cConnectionType() == dbictTcpProxy) { const ConnectionProxy &p(cConnectionProxy()); socket.setProxy(QNetworkProxy(QNetworkProxy::Socks5Proxy, p.host, p.port, p.user, p.password)); } else { socket.setProxy(QNetworkProxy(QNetworkProxy::NoProxy)); } +#endif } QImage **cornersMask() { diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index c01fdf9802..3b53eb0611 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -16,13 +16,13 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "types.h" +#include "basic_types.h" -class Application; +class AppClass; class Window; class MainWidget; class SettingsWidget; @@ -36,30 +36,18 @@ class FileUploader; typedef QMap HistoryItemsMap; typedef QHash PhotoItems; -typedef QHash VideoItems; -typedef QHash AudioItems; typedef QHash DocumentItems; typedef QHash WebPageItems; typedef QHash SharedContactItems; typedef QHash GifItems; typedef QHash PhotosData; -typedef QHash VideosData; -typedef QHash AudiosData; typedef QHash DocumentsData; -struct ReplyMarkup { - ReplyMarkup(int32 flags = 0) : flags(flags) { - } - typedef QList > Commands; - Commands commands; - int32 flags; -}; - class LayeredWidget; namespace App { - Application *app(); + AppClass *app(); Window *wnd(); MainWidget *main(); SettingsWidget *settings(); @@ -68,7 +56,6 @@ namespace App { ApiWrap *api(); void logOut(); - bool loggedOut(); QString formatPhone(QString phone); @@ -85,6 +72,7 @@ namespace App { void feedChatAdmins(const MTPDupdateChatAdmins &d, bool emitPeerUpdated = true); void feedParticipantAdmin(const MTPDupdateChatParticipantAdmin &d, bool emitPeerUpdated = true); bool checkEntitiesAndViewsUpdate(const MTPDmessage &m); // returns true if item found and it is not detached + void updateEditedMessage(const MTPDmessage &m); void addSavedGif(DocumentData *doc); void checkSavedGif(HistoryItem *item); void feedMsgs(const QVector &msgs, NewMessageType type); @@ -106,9 +94,6 @@ namespace App { PhotoData *feedPhoto(const MTPPhoto &photo, const PreparedPhotoThumbs &thumbs); PhotoData *feedPhoto(const MTPPhoto &photo, PhotoData *convert = 0); PhotoData *feedPhoto(const MTPDphoto &photo, PhotoData *convert = 0); - VideoData *feedVideo(const MTPDvideo &video, VideoData *convert = 0); - AudioData *feedAudio(const MTPaudio &audio, AudioData *convert = 0); - AudioData *feedAudio(const MTPDaudio &audio, AudioData *convert = 0); DocumentData *feedDocument(const MTPdocument &document, const QPixmap &thumb); DocumentData *feedDocument(const MTPdocument &document, DocumentData *convert = 0); DocumentData *feedDocument(const MTPDdocument &document, DocumentData *convert = 0); @@ -116,36 +101,57 @@ namespace App { WebPageData *feedWebPage(const MTPDwebPagePending &webpage, WebPageData *convert = 0); WebPageData *feedWebPage(const MTPWebPage &webpage); - PeerData *peerLoaded(const PeerId &id); - UserData *userLoaded(const PeerId &id); - ChatData *chatLoaded(const PeerId &id); - ChannelData *channelLoaded(const PeerId &id); - UserData *userLoaded(int32 user); - ChatData *chatLoaded(int32 chat); - ChannelData *channelLoaded(int32 channel); + PeerData *peer(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded); + inline UserData *user(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) { + return asUser(peer(id, restriction)); + } + inline ChatData *chat(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) { + return asChat(peer(id, restriction)); + } + inline ChannelData *channel(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) { + return asChannel(peer(id, restriction)); + } + inline UserData *user(UserId userId, PeerData::LoadedStatus restriction = PeerData::NotLoaded) { + return asUser(peer(peerFromUser(userId), restriction)); + } + inline ChatData *chat(ChatId chatId, PeerData::LoadedStatus restriction = PeerData::NotLoaded) { + return asChat(peer(peerFromChat(chatId), restriction)); + } + inline ChannelData *channel(ChannelId channelId, PeerData::LoadedStatus restriction = PeerData::NotLoaded) { + return asChannel(peer(peerFromChannel(channelId), restriction)); + } + inline PeerData *peerLoaded(const PeerId &id) { + return peer(id, PeerData::FullLoaded); + } + inline UserData *userLoaded(const PeerId &id) { + return user(id, PeerData::FullLoaded); + } + inline ChatData *chatLoaded(const PeerId &id) { + return chat(id, PeerData::FullLoaded); + } + inline ChannelData *channelLoaded(const PeerId &id) { + return channel(id, PeerData::FullLoaded); + } + inline UserData *userLoaded(UserId userId) { + return user(userId, PeerData::FullLoaded); + } + inline ChatData *chatLoaded(ChatId chatId) { + return chat(chatId, PeerData::FullLoaded); + } + inline ChannelData *channelLoaded(ChannelId channelId) { + return channel(channelId, PeerData::FullLoaded); + } - PeerData *peer(const PeerId &id); - UserData *user(const PeerId &id); - ChatData *chat(const PeerId &id); - ChannelData *channel(const PeerId &id); - UserData *user(int32 user_id); - ChatData *chat(int32 chat_id); - ChannelData *channel(int32 channel_id); UserData *self(); PeerData *peerByName(const QString &username); QString peerName(const PeerData *peer, bool forDialogs = false); PhotoData *photo(const PhotoId &photo); PhotoData *photoSet(const PhotoId &photo, PhotoData *convert, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full); - VideoData *video(const VideoId &video); - VideoData *videoSet(const VideoId &video, VideoData *convert, const uint64 &access, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size); - AudioData *audio(const AudioId &audio); - AudioData *audioSet(const AudioId &audio, AudioData *convert, const uint64 &access, int32 date, const QString &mime, int32 duration, int32 dc, int32 size); DocumentData *document(const DocumentId &document); DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation); WebPageData *webPage(const WebPageId &webPage); WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const QString &description, PhotoData *photo, DocumentData *doc, int32 duration, const QString &author, int32 pendingTill); - ImageLinkData *imageLink(const QString &imageLink); - ImageLinkData *imageLinkSet(const QString &imageLink, ImageLinkType type, const QString &url); + LocationData *location(const LocationCoords &coords); void forgetMedia(); MTPPhoto photoFromUserPhoto(MTPint userId, MTPint date, const MTPUserProfilePhoto &photo); @@ -155,16 +161,27 @@ namespace App { History *historyFromDialog(const PeerId &peer, int32 unreadCnt, int32 maxInboxRead); History *historyLoaded(const PeerId &peer); HistoryItem *histItemById(ChannelId channelId, MsgId itemId); + inline History *history(const PeerData *peer) { + t_assert(peer != nullptr); + return history(peer->id); + } + inline History *historyLoaded(const PeerData *peer) { + return peer ? historyLoaded(peer->id) : nullptr; + } + inline HistoryItem *histItemById(const ChannelData *channel, MsgId itemId) { + return histItemById(channel ? peerToChannel(channel->id) : 0, itemId); + } inline HistoryItem *histItemById(const FullMsgId &msgId) { return histItemById(msgId.channel, msgId.msg); } void historyRegItem(HistoryItem *item); void historyItemDetached(HistoryItem *item); void historyUnregItem(HistoryItem *item); + void historyUpdateDependent(HistoryItem *item); void historyClearMsgs(); void historyClearItems(); - void historyRegReply(HistoryReply *reply, HistoryItem *to); - void historyUnregReply(HistoryReply *reply, HistoryItem *to); + void historyRegDependency(HistoryItem *dependent, HistoryItem *dependency); + void historyUnregDependency(HistoryItem *dependent, HistoryItem *dependency); void historyRegRandom(uint64 randomId, const FullMsgId &itemId); void historyUnregRandom(uint64 randomId); @@ -202,9 +219,15 @@ namespace App { bool isValidPhone(QString phone); + enum LaunchState { + Launched = 0, + QuitRequested = 1, + QuitProcessed = 2, + }; void quit(); - bool quiting(); - void setQuiting(); + bool quitting(); + LaunchState launchState(); + void setLaunchState(LaunchState state); QImage readImage(QByteArray data, QByteArray *format = 0, bool opaque = true, bool *animated = 0); QImage readImage(const QString &file, QByteArray *format = 0, bool opaque = true, bool *animated = 0, QByteArray *content = 0); @@ -214,16 +237,6 @@ namespace App { const PhotoItems &photoItems(); const PhotosData &photosData(); - void regVideoItem(VideoData *data, HistoryItem *item); - void unregVideoItem(VideoData *data, HistoryItem *item); - const VideoItems &videoItems(); - const VideosData &videosData(); - - void regAudioItem(AudioData *data, HistoryItem *item); - void unregAudioItem(AudioData*data, HistoryItem *item); - const AudioItems &audioItems(); - const AudiosData &audiosData(); - void regDocumentItem(DocumentData *data, HistoryItem *item); void unregDocumentItem(DocumentData *data, HistoryItem *item); const DocumentItems &documentItems(); @@ -246,16 +259,10 @@ namespace App { void unregMuted(PeerData *peer); void updateMuted(); - void regInlineResultLoader(FileLoader *loader, InlineResult *result); - void unregInlineResultLoader(FileLoader *loader); - InlineResult *inlineResultFromLoader(FileLoader *loader); - - void feedReplyMarkup(ChannelId channelId, MsgId msgId, const MTPReplyMarkup &markup); - void clearReplyMarkup(ChannelId channelId, MsgId msgId); - const ReplyMarkup &replyMarkup(ChannelId channelId, MsgId msgId); - void setProxySettings(QNetworkAccessManager &manager); +#ifndef TDESKTOP_DISABLE_NETWORK_PROXY QNetworkProxy getHttpProxySettings(); +#endif void setProxySettings(QTcpSocket &socket); QImage **cornersMask(); diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index ca89de07c0..38180bfd19 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -16,11 +16,13 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "application.h" -#include "style.h" +#include "gui/style.h" + +#include "shortcuts.h" #include "pspecific.h" #include "fileuploader.h" @@ -35,10 +37,6 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "autoupdater.h" namespace { - Application *mainApp = 0; - FileUploader *uploader = 0; - QString lng; - void mtpStateChanged(int32 dc, int32 state) { if (App::wnd()) { App::wnd()->mtpStateChanged(dc, state); @@ -51,545 +49,6 @@ namespace { } } - class EventFilterForKeys : public QObject { - public: - - EventFilterForKeys(QObject *parent) : QObject(parent) { - - } - bool eventFilter(QObject *o, QEvent *e) { - if (e->type() == QEvent::KeyPress) { - QKeyEvent *ev = static_cast(e); - if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { - if (ev->key() == Qt::Key_W && (ev->modifiers() & Qt::ControlModifier)) { - if (cWorkMode() == dbiwmTrayOnly || cWorkMode() == dbiwmWindowAndTray) { - App::wnd()->minimizeToTray(); - return true; - } else { - App::wnd()->hide(); - App::wnd()->updateIsActive(cOfflineBlurTimeout()); - App::wnd()->updateGlobalMenu(); - return true; - } - } else if (ev->key() == Qt::Key_M && (ev->modifiers() & Qt::ControlModifier)) { - App::wnd()->setWindowState(Qt::WindowMinimized); - return true; - } - } - if (ev->key() == Qt::Key_MediaPlay) { - if (App::main()) App::main()->player()->playPressed(); - } else if (ev->key() == Qt::Key_MediaPause) { - if (App::main()) App::main()->player()->pausePressed(); - } else if (ev->key() == Qt::Key_MediaTogglePlayPause) { - if (App::main()) App::main()->player()->playPausePressed(); - } else if (ev->key() == Qt::Key_MediaStop) { - if (App::main()) App::main()->player()->stopPressed(); - } else if (ev->key() == Qt::Key_MediaPrevious) { - if (App::main()) App::main()->player()->prevPressed(); - } else if (ev->key() == Qt::Key_MediaNext) { - if (App::main()) App::main()->player()->nextPressed(); - } - } - return QObject::eventFilter(o, e); - } - - }; -} - -Application::Application(int &argc, char **argv) : PsApplication(argc, argv), - serverName(psServerPrefix() + cGUIDStr()), closing(false), - #ifndef TDESKTOP_DISABLE_AUTOUPDATE - updateRequestId(0), updateReply(0), updateThread(0), updateDownloader(0), - #endif - _translator(0) { - - DEBUG_LOG(("Application Info: creation..")); - - QByteArray d(QDir((cPlatform() == dbipWindows ? cExeDir() : cWorkingDir()).toLower()).absolutePath().toUtf8()); - char h[33] = { 0 }; - hashMd5Hex(d.constData(), d.size(), h); - serverName = psServerPrefix() + h + '-' + cGUIDStr(); - - if (mainApp) { - DEBUG_LOG(("Application Error: another Application was created, terminating..")); - exit(0); - } - mainApp = this; - - installEventFilter(new EventFilterForKeys(this)); - - QFontDatabase::addApplicationFont(qsl(":/gui/art/fonts/OpenSans-Regular.ttf")); - QFontDatabase::addApplicationFont(qsl(":/gui/art/fonts/OpenSans-Bold.ttf")); - QFontDatabase::addApplicationFont(qsl(":/gui/art/fonts/OpenSans-Semibold.ttf")); - - float64 dpi = primaryScreen()->logicalDotsPerInch(); - if (dpi <= 108) { // 0-96-108 - cSetScreenScale(dbisOne); - } else if (dpi <= 132) { // 108-120-132 - cSetScreenScale(dbisOneAndQuarter); - } else if (dpi <= 168) { // 132-144-168 - cSetScreenScale(dbisOneAndHalf); - } else { // 168-192-inf - cSetScreenScale(dbisTwo); - } - - if (devicePixelRatio() > 1) { - cSetRetina(true); - cSetRetinaFactor(devicePixelRatio()); - cSetIntRetinaFactor(int32(cRetinaFactor())); - cSetConfigScale(dbisOne); - cSetRealScale(dbisOne); - } - - if (cLang() < languageTest) { - cSetLang(languageId()); - } - if (cLang() == languageTest) { - if (QFileInfo(cLangFile()).exists()) { - LangLoaderPlain loader(cLangFile()); - cSetLangErrors(loader.errors()); - if (!cLangErrors().isEmpty()) { - LOG(("Lang load errors: %1").arg(cLangErrors())); - } else if (!loader.warnings().isEmpty()) { - LOG(("Lang load warnings: %1").arg(loader.warnings())); - } - } else { - cSetLang(languageDefault); - } - } else if (cLang() > languageDefault && cLang() < languageCount) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[cLang()] + qsl(".strings")); - if (!loader.errors().isEmpty()) { - LOG(("Lang load errors: %1").arg(loader.errors())); - } else if (!loader.warnings().isEmpty()) { - LOG(("Lang load warnings: %1").arg(loader.warnings())); - } - } - - installTranslator(_translator = new Translator()); - - style::startManager(); - anim::startManager(); - historyInit(); - - DEBUG_LOG(("Application Info: inited..")); - - window = new Window(); - - psInstallEventFilter(); - - connect(&socket, SIGNAL(connected()), this, SLOT(socketConnected())); - connect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); - connect(&socket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(socketError(QLocalSocket::LocalSocketError))); - connect(&socket, SIGNAL(bytesWritten(qint64)), this, SLOT(socketWritten(qint64))); - connect(&socket, SIGNAL(readyRead()), this, SLOT(socketReading())); - connect(&server, SIGNAL(newConnection()), this, SLOT(newInstanceConnected())); - connect(this, SIGNAL(aboutToQuit()), this, SLOT(closeApplication())); - #ifndef TDESKTOP_DISABLE_AUTOUPDATE - connect(&updateCheckTimer, SIGNAL(timeout()), this, SLOT(startUpdateCheck())); - connect(this, SIGNAL(updateFailed()), this, SLOT(onUpdateFailed())); - connect(this, SIGNAL(updateReady()), this, SLOT(onUpdateReady())); - #endif - connect(this, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(onAppStateChanged(Qt::ApplicationState))); - - connect(&_mtpUnpauseTimer, SIGNAL(timeout()), this, SLOT(doMtpUnpause())); - - connect(&killDownloadSessionsTimer, SIGNAL(timeout()), this, SLOT(killDownloadSessions())); - - if (cManyInstance()) { - startApp(); - } else { - DEBUG_LOG(("Application Info: connecting local socket to %1..").arg(serverName)); - socket.connectToServer(serverName); - } -} - -#ifndef TDESKTOP_DISABLE_AUTOUPDATE -void Application::updateGotCurrent() { - if (!updateReply || updateThread) return; - - cSetLastUpdateCheck(unixtime()); - QRegularExpressionMatch m = QRegularExpression(qsl("^\\s*(\\d+)\\s*:\\s*([\\x21-\\x7f]+)\\s*$")).match(QString::fromUtf8(updateReply->readAll())); - if (m.hasMatch()) { - uint64 currentVersion = m.captured(1).toULongLong(); - QString url = m.captured(2); - bool betaVersion = false; - if (url.startsWith(qstr("beta_"))) { - betaVersion = true; - url = url.mid(5) + '_' + countBetaVersionSignature(currentVersion); - } - if ((!betaVersion || cBetaVersion()) && currentVersion > (betaVersion ? cBetaVersion() : uint64(AppVersion))) { - updateThread = new QThread(); - connect(updateThread, SIGNAL(finished()), updateThread, SLOT(deleteLater())); - updateDownloader = new UpdateDownloader(updateThread, url); - updateThread->start(); - } - } - if (updateReply) updateReply->deleteLater(); - updateReply = 0; - if (!updateThread) { - QDir updates(cWorkingDir() + "tupdates"); - if (updates.exists()) { - QFileInfoList list = updates.entryInfoList(QDir::Files); - for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) { - if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) { - QFile(i->absoluteFilePath()).remove(); - } - } - } - emit updateLatest(); - } - startUpdateCheck(true); - Local::writeSettings(); -} - -void Application::updateFailedCurrent(QNetworkReply::NetworkError e) { - LOG(("App Error: could not get current version (update check): %1").arg(e)); - if (updateReply) updateReply->deleteLater(); - updateReply = 0; - - emit updateFailed(); - startUpdateCheck(true); -} - -void Application::onUpdateReady() { - if (updateDownloader) { - updateDownloader->deleteLater(); - updateDownloader = 0; - } - updateCheckTimer.stop(); - - cSetLastUpdateCheck(unixtime()); - Local::writeSettings(); -} - -void Application::onUpdateFailed() { - if (updateDownloader) { - updateDownloader->deleteLater(); - updateDownloader = 0; - if (updateThread) updateThread->quit(); - updateThread = 0; - } - - cSetLastUpdateCheck(unixtime()); - Local::writeSettings(); -} -#endif - -void Application::regPhotoUpdate(const PeerId &peer, const FullMsgId &msgId) { - photoUpdates.insert(msgId, peer); -} - -void Application::clearPhotoUpdates() { - photoUpdates.clear(); -} - -bool Application::isPhotoUpdating(const PeerId &peer) { - for (QMap::iterator i = photoUpdates.begin(), e = photoUpdates.end(); i != e; ++i) { - if (i.value() == peer) { - return true; - } - } - return false; -} - -void Application::cancelPhotoUpdate(const PeerId &peer) { - for (QMap::iterator i = photoUpdates.begin(), e = photoUpdates.end(); i != e;) { - if (i.value() == peer) { - i = photoUpdates.erase(i); - } else { - ++i; - } - } -} - -void Application::mtpPause() { - MTP::pause(); - _mtpUnpauseTimer.start(st::slideDuration * 2); -} - -void Application::mtpUnpause() { - _mtpUnpauseTimer.start(1); -} - -void Application::doMtpUnpause() { - MTP::unpause(); -} - -void Application::selfPhotoCleared(const MTPUserProfilePhoto &result) { - if (!App::self()) return; - App::self()->setPhoto(result); - emit peerPhotoDone(App::self()->id); -} - -void Application::chatPhotoCleared(PeerId peer, const MTPUpdates &updates) { - if (App::main()) { - App::main()->sentUpdatesReceived(updates); - } - cancelPhotoUpdate(peer); - emit peerPhotoDone(peer); -} - -void Application::selfPhotoDone(const MTPphotos_Photo &result) { - if (!App::self()) return; - const MTPDphotos_photo &photo(result.c_photos_photo()); - App::feedPhoto(photo.vphoto); - App::feedUsers(photo.vusers); - cancelPhotoUpdate(App::self()->id); - emit peerPhotoDone(App::self()->id); -} - -void Application::chatPhotoDone(PeerId peer, const MTPUpdates &updates) { - if (App::main()) { - App::main()->sentUpdatesReceived(updates); - } - cancelPhotoUpdate(peer); - emit peerPhotoDone(peer); -} - -bool Application::peerPhotoFail(PeerId peer, const RPCError &error) { - if (mtpIsFlood(error)) return false; - - LOG(("Application Error: update photo failed %1: %2").arg(error.type()).arg(error.description())); - cancelPhotoUpdate(peer); - emit peerPhotoFail(peer); - return true; -} - -void Application::peerClearPhoto(PeerId id) { - if (MTP::authedId() && peerToUser(id) == MTP::authedId()) { - MTP::send(MTPphotos_UpdateProfilePhoto(MTP_inputPhotoEmpty(), MTP_inputPhotoCropAuto()), rpcDone(&Application::selfPhotoCleared), rpcFail(&Application::peerPhotoFail, id)); - } else if (peerIsChat(id)) { - MTP::send(MTPmessages_EditChatPhoto(peerToBareMTPInt(id), MTP_inputChatPhotoEmpty()), rpcDone(&Application::chatPhotoCleared, id), rpcFail(&Application::peerPhotoFail, id)); - } else if (peerIsChannel(id)) { - if (ChannelData *channel = App::channelLoaded(id)) { - MTP::send(MTPchannels_EditPhoto(channel->inputChannel, MTP_inputChatPhotoEmpty()), rpcDone(&Application::chatPhotoCleared, id), rpcFail(&Application::peerPhotoFail, id)); - } - } -} - -void Application::killDownloadSessionsStart(int32 dc) { - if (killDownloadSessionTimes.constFind(dc) == killDownloadSessionTimes.cend()) { - killDownloadSessionTimes.insert(dc, getms() + MTPAckSendWaiting + MTPKillFileSessionTimeout); - } - if (!killDownloadSessionsTimer.isActive()) { - killDownloadSessionsTimer.start(MTPAckSendWaiting + MTPKillFileSessionTimeout + 5); - } -} - -void Application::killDownloadSessionsStop(int32 dc) { - killDownloadSessionTimes.remove(dc); - if (killDownloadSessionTimes.isEmpty() && killDownloadSessionsTimer.isActive()) { - killDownloadSessionsTimer.stop(); - } -} - -void Application::checkLocalTime() { - if (App::main()) App::main()->checkLastUpdate(checkms()); -} - -void Application::onAppStateChanged(Qt::ApplicationState state) { - checkLocalTime(); - if (window) window->updateIsActive((state == Qt::ApplicationActive) ? cOnlineFocusTimeout() : cOfflineBlurTimeout()); -} - -void Application::killDownloadSessions() { - uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout; - for (QMap::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) { - if (i.value() <= ms) { - for (int j = 0; j < MTPDownloadSessionsCount; ++j) { - MTP::stopSession(MTP::dld[j] + i.key()); - } - i = killDownloadSessionTimes.erase(i); - } else { - if (i.value() - ms < left) { - left = i.value() - ms; - } - ++i; - } - } - if (!killDownloadSessionTimes.isEmpty()) { - killDownloadSessionsTimer.start(left); - } -} - -void Application::photoUpdated(const FullMsgId &msgId, const MTPInputFile &file) { - if (!App::self()) return; - - QMap::iterator i = photoUpdates.find(msgId); - if (i != photoUpdates.end()) { - PeerId id = i.value(); - if (MTP::authedId() && peerToUser(id) == MTP::authedId()) { - MTP::send(MTPphotos_UploadProfilePhoto(file, MTP_string(""), MTP_inputGeoPointEmpty(), MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100))), rpcDone(&Application::selfPhotoDone), rpcFail(&Application::peerPhotoFail, id)); - } else if (peerIsChat(id)) { - History *hist = App::history(id); - hist->sendRequestId = MTP::send(MTPmessages_EditChatPhoto(hist->peer->asChat()->inputChat, MTP_inputChatUploadedPhoto(file, MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100)))), rpcDone(&Application::chatPhotoDone, id), rpcFail(&Application::peerPhotoFail, id), 0, 0, hist->sendRequestId); - } else if (peerIsChannel(id)) { - History *hist = App::history(id); - hist->sendRequestId = MTP::send(MTPchannels_EditPhoto(hist->peer->asChannel()->inputChannel, MTP_inputChatUploadedPhoto(file, MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100)))), rpcDone(&Application::chatPhotoDone, id), rpcFail(&Application::peerPhotoFail, id), 0, 0, hist->sendRequestId); - } - } -} - -void Application::onSwitchDebugMode() { - if (cDebug()) { - QFile(cWorkingDir() + qsl("tdata/withdebug")).remove(); - cSetDebug(false); - cSetRestarting(true); - cSetRestartingToSettings(true); - App::quit(); - } else { - logsInitDebug(); - cSetDebug(true); - QFile f(cWorkingDir() + qsl("tdata/withdebug")); - if (f.open(QIODevice::WriteOnly)) { - f.write("1"); - f.close(); - } - Ui::hideLayer(); - } -} - -void Application::onSwitchTestMode() { - if (cTestMode()) { - QFile(cWorkingDir() + qsl("tdata/withtestmode")).remove(); - cSetTestMode(false); - } else { - QFile f(cWorkingDir() + qsl("tdata/withtestmode")); - if (f.open(QIODevice::WriteOnly)) { - f.write("1"); - f.close(); - } - cSetTestMode(true); - } - cSetRestarting(true); - cSetRestartingToSettings(true); - App::quit(); -} - -Application::UpdatingState Application::updatingState() { - #ifndef TDESKTOP_DISABLE_AUTOUPDATE - if (!updateThread) return Application::UpdatingNone; - if (!updateDownloader) return Application::UpdatingReady; - return Application::UpdatingDownload; - #else - return Application::UpdatingNone; - #endif -} - -#ifndef TDESKTOP_DISABLE_AUTOUPDATE -int32 Application::updatingSize() { - if (!updateDownloader) return 0; - return updateDownloader->size(); -} - -int32 Application::updatingReady() { - if (!updateDownloader) return 0; - return updateDownloader->ready(); -} -#endif - -FileUploader *Application::uploader() { - if (!::uploader && !App::quiting()) ::uploader = new FileUploader(); - return ::uploader; -} - -void Application::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) { - PreparedPhotoThumbs photoThumbs; - QVector photoSizes; - - QPixmap thumb = QPixmap::fromImage(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); - photoThumbs.insert('a', thumb); - photoSizes.push_back(MTP_photoSize(MTP_string("a"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0))); - - QPixmap medium = QPixmap::fromImage(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); - photoThumbs.insert('b', medium); - photoSizes.push_back(MTP_photoSize(MTP_string("b"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0))); - - QPixmap full = QPixmap::fromImage(tosend, Qt::ColorOnly); - photoThumbs.insert('c', full); - photoSizes.push_back(MTP_photoSize(MTP_string("c"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0))); - - QByteArray jpeg; - QBuffer jpegBuffer(&jpeg); - full.save(&jpegBuffer, "JPG", 87); - - PhotoId id = MTP::nonce(); - - MTPPhoto photo(MTP_photo(MTP_long(id), MTP_long(0), MTP_int(unixtime()), MTP_vector(photoSizes))); - - QString file, filename; - int32 filesize = 0; - QByteArray data; - - ReadyLocalMedia ready(PreparePhoto, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, MTP_audioEmpty(MTP_long(0)), photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, false, false, 0); - - connect(App::uploader(), SIGNAL(photoReady(const FullMsgId&, const MTPInputFile&)), App::app(), SLOT(photoUpdated(const FullMsgId&, const MTPInputFile&)), Qt::UniqueConnection); - - FullMsgId newId(peerToChannel(peerId), clientMsgId()); - App::app()->regPhotoUpdate(peerId, newId); - App::uploader()->uploadMedia(newId, ready); -} - -#ifndef TDESKTOP_DISABLE_AUTOUPDATE -void Application::stopUpdate() { - if (updateReply) { - updateReply->abort(); - updateReply->deleteLater(); - updateReply = 0; - } - if (updateDownloader) { - updateDownloader->deleteLater(); - updateDownloader = 0; - if (updateThread) updateThread->quit(); - updateThread = 0; - } -} - -void Application::startUpdateCheck(bool forceWait) { - updateCheckTimer.stop(); - if (updateRequestId || updateThread || updateReply || !cAutoUpdate()) return; - - int32 constDelay = cBetaVersion() ? 600 : UpdateDelayConstPart, randDelay = cBetaVersion() ? 300 : UpdateDelayRandPart; - int32 updateInSecs = cLastUpdateCheck() + constDelay + int32(MTP::nonce() % randDelay) - unixtime(); - bool sendRequest = (updateInSecs <= 0 || updateInSecs > (constDelay + randDelay)); - if (!sendRequest && !forceWait) { - QDir updates(cWorkingDir() + "tupdates"); - if (updates.exists()) { - QFileInfoList list = updates.entryInfoList(QDir::Files); - for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) { - if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) { - sendRequest = true; - } - } - } - } - if (cManyInstance() && !cDebug()) return; // only main instance is updating - - if (sendRequest) { - QUrl url(cUpdateURL()); - if (cBetaVersion()) { - url.setQuery(qsl("version=%1&beta=%2").arg(AppVersion).arg(cBetaVersion())); - } else if (cDevVersion()) { - url.setQuery(qsl("version=%1&dev=1").arg(AppVersion)); - } else { - url.setQuery(qsl("version=%1").arg(AppVersion)); - } - QString u = url.toString(); - QNetworkRequest checkVersion(url); - if (updateReply) updateReply->deleteLater(); - - App::setProxySettings(updateManager); - updateReply = updateManager.get(checkVersion); - connect(updateReply, SIGNAL(finished()), this, SLOT(updateGotCurrent())); - connect(updateReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(updateFailedCurrent(QNetworkReply::NetworkError))); - emit updateChecking(); - } else { - updateCheckTimer.start((updateInSecs + 5) * 1000); - } -} -#endif - -namespace { QChar _toHex(ushort v) { v = v & 0x000F; return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v)); @@ -629,9 +88,50 @@ namespace { } } +AppClass *AppObject = 0; + +Application::Application(int &argc, char **argv) : QApplication(argc, argv) +, _secondInstance(false) +#ifndef TDESKTOP_DISABLE_AUTOUPDATE +, _updateReply(0) +, _updateThread(0) +, _updateChecker(0) +#endif +{ + QByteArray d(QFile::encodeName(QDir(cWorkingDir()).absolutePath())); + char h[33] = { 0 }; + hashMd5Hex(d.constData(), d.size(), h); + _localServerName = psServerPrefix() + h + '-' + cGUIDStr(); + + connect(&_localSocket, SIGNAL(connected()), this, SLOT(socketConnected())); + connect(&_localSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + connect(&_localSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(socketError(QLocalSocket::LocalSocketError))); + connect(&_localSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(socketWritten(qint64))); + connect(&_localSocket, SIGNAL(readyRead()), this, SLOT(socketReading())); + connect(&_localServer, SIGNAL(newConnection()), this, SLOT(newInstanceConnected())); + + QTimer::singleShot(0, this, SLOT(startApplication())); + connect(this, SIGNAL(aboutToQuit()), this, SLOT(closeApplication())); + +#ifndef TDESKTOP_DISABLE_AUTOUPDATE + connect(&_updateCheckTimer, SIGNAL(timeout()), this, SLOT(updateCheck())); + connect(this, SIGNAL(updateFailed()), this, SLOT(onUpdateFailed())); + connect(this, SIGNAL(updateReady()), this, SLOT(onUpdateReady())); +#endif + + if (cManyInstance()) { + LOG(("Many instance allowed, starting...")); + singleInstanceChecked(); + } else { + LOG(("Connecting local socket to %1...").arg(_localServerName)); + _localSocket.connectToServer(_localServerName); + } +} + void Application::socketConnected() { - DEBUG_LOG(("Application Info: socket connected, this is not the first application instance, sending show command..")); - closing = true; + LOG(("Socket connected, this is not the first application instance, sending show command...")); + _secondInstance = true; + QString commands; const QStringList &lst(cSendPaths()); for (QStringList::const_iterator i = lst.cbegin(), e = lst.cend(); i != e; ++i) { @@ -641,154 +141,109 @@ void Application::socketConnected() { commands += qsl("OPEN:") + _escapeTo7bit(cStartUrl()) + ';'; } commands += qsl("CMD:show;"); + DEBUG_LOG(("Application Info: writing commands %1").arg(commands)); - socket.write(commands.toLocal8Bit()); + _localSocket.write(commands.toLatin1()); } void Application::socketWritten(qint64/* bytes*/) { - if (socket.state() != QLocalSocket::ConnectedState) { - DEBUG_LOG(("Application Error: socket is not connected %1").arg(socket.state())); + if (_localSocket.state() != QLocalSocket::ConnectedState) { + LOG(("Socket is not connected %1").arg(_localSocket.state())); return; } - if (socket.bytesToWrite()) { + if (_localSocket.bytesToWrite()) { return; } - DEBUG_LOG(("Application Info: show command written, waiting response..")); + LOG(("Show command written, waiting response...")); } void Application::socketReading() { - if (socket.state() != QLocalSocket::ConnectedState) { - DEBUG_LOG(("Application Error: socket is not connected %1").arg(socket.state())); + if (_localSocket.state() != QLocalSocket::ConnectedState) { + LOG(("Socket is not connected %1").arg(_localSocket.state())); return; } - socketRead.append(socket.readAll()); - if (QRegularExpression("RES:(\\d+);").match(socketRead).hasMatch()) { - uint64 pid = socketRead.mid(4, socketRead.length() - 5).toULongLong(); + _localSocketReadData.append(_localSocket.readAll()); + if (QRegularExpression("RES:(\\d+);").match(_localSocketReadData).hasMatch()) { + uint64 pid = _localSocketReadData.mid(4, _localSocketReadData.length() - 5).toULongLong(); psActivateProcess(pid); - DEBUG_LOG(("Application Info: show command response received, pid = %1, activating and quiting..").arg(pid)); + LOG(("Show command response received, pid = %1, activating and quitting...").arg(pid)); return App::quit(); } } void Application::socketError(QLocalSocket::LocalSocketError e) { - if (closing) { - DEBUG_LOG(("Application Error: could not write show command, error %1, quiting..").arg(e)); + if (App::quitting()) return; + + if (_secondInstance) { + LOG(("Could not write show command, error %1, quitting...").arg(e)); return App::quit(); } if (e == QLocalSocket::ServerNotFoundError) { - DEBUG_LOG(("Application Info: this is the only instance of Telegram, starting server and app..")); + LOG(("This is the only instance of Telegram, starting server and app...")); } else { - DEBUG_LOG(("Application Info: socket connect error %1, starting server and app..").arg(e)); + LOG(("Socket connect error %1, starting server and app...").arg(e)); } - socket.close(); + _localSocket.close(); - psCheckLocalSocket(serverName); + psCheckLocalSocket(_localServerName); - if (!server.listen(serverName)) { - DEBUG_LOG(("Application Error: failed to start listening to %1 server, error %2").arg(serverName).arg(int(server.serverError()))); + if (!_localServer.listen(_localServerName)) { + LOG(("Failed to start listening to %1 server, error %2").arg(_localServerName).arg(int(_localServer.serverError()))); return App::quit(); } - #ifndef TDESKTOP_DISABLE_AUTOUPDATE +#ifndef TDESKTOP_DISABLE_AUTOUPDATE if (!cNoStartUpdate() && checkReadyUpdate()) { cSetRestartingUpdate(true); - DEBUG_LOG(("Application Info: installing update instead of starting app..")); + DEBUG_LOG(("Application Info: installing update instead of starting app...")); return App::quit(); } - #endif +#endif - startApp(); + singleInstanceChecked(); } -void Application::checkMapVersion() { - if (Local::oldMapVersion() < AppVersion) { - if (Local::oldMapVersion()) { - QString versionFeatures; - if (cDevVersion() && Local::oldMapVersion() < 9016) { -// versionFeatures = QString::fromUtf8("\xe2\x80\x94 Sticker management: manually rearrange your sticker packs, pack order is now synced across all your devices\n\xe2\x80\x94 Click and hold on a sticker to preview it before sending\n\xe2\x80\x94 New context menu for chats in chats list\n\xe2\x80\x94 Support for all existing emoji");// .replace('@', qsl("@") + QChar(0x200D)); - versionFeatures = lng_new_version_text(lt_gifs_link, qsl("https://telegram.org/blog/gif-revolution"), lt_bots_link, qsl("https://telegram.org/blog/inline-bots")).trimmed(); - } else if (Local::oldMapVersion() < 9016) { - versionFeatures = lng_new_version_text(lt_gifs_link, qsl("https://telegram.org/blog/gif-revolution"), lt_bots_link, qsl("https://telegram.org/blog/inline-bots")).trimmed(); +void Application::singleInstanceChecked() { + if (cManyInstance()) { + Logs::multipleInstances(); + } + + Sandbox::start(); + + if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) { + new NotStartedWindow(); + } else { + SignalHandlers::Status status = SignalHandlers::start(); + if (status == SignalHandlers::CantOpen) { + new NotStartedWindow(); + } else if (status == SignalHandlers::LastCrashed) { + if (Sandbox::LastCrashDump().isEmpty()) { // don't handle bad closing for now + if (SignalHandlers::restart() == SignalHandlers::CantOpen) { + new NotStartedWindow(); + } else { + Sandbox::launch(); + } } else { - versionFeatures = lang(lng_new_version_minor).trimmed(); + new LastCrashedWindow(); } - if (!versionFeatures.isEmpty()) { - versionFeatures = lng_new_version_wrap(lt_version, QString::fromStdWString(AppVersionStr), lt_changes, versionFeatures, lt_link, qsl("https://desktop.telegram.org/#changelog")); - window->serviceNotification(versionFeatures); - } - } - } - if (cNeedConfigResave()) { - Local::writeUserSettings(); - } -} - -void Application::startApp() { - cChangeTimeFormat(QLocale::system().timeFormat(QLocale::ShortFormat)); - - DEBUG_LOG(("Application Info: starting app..")); - - QMimeDatabase().mimeTypeForName(qsl("text/plain")); // create mime database - - window->createWinId(); - window->init(); - - DEBUG_LOG(("Application Info: window created..")); - - initImageLinkManager(); - App::initMedia(); - - Local::ReadMapState state = Local::readMap(QByteArray()); - if (state == Local::ReadMapPassNeeded) { - cSetHasPasscode(true); - DEBUG_LOG(("Application Info: passcode nneded..")); - } else { - DEBUG_LOG(("Application Info: local map read..")); - MTP::start(); - } - - MTP::setStateChangedHandler(mtpStateChanged); - MTP::setSessionResetHandler(mtpSessionReset); - - DEBUG_LOG(("Application Info: MTP started..")); - - DEBUG_LOG(("Application Info: showing.")); - if (state == Local::ReadMapPassNeeded) { - window->setupPasscode(false); - } else { - if (MTP::authedId()) { - window->setupMain(false); } else { - window->setupIntro(false); + Sandbox::launch(); } } - window->firstShow(); - - if (cStartToSettings()) { - window->showSettings(); - } - - QNetworkProxyFactory::setUseSystemConfiguration(true); - - if (state != Local::ReadMapPassNeeded) { - checkMapVersion(); - } - - window->updateIsActive(cOnlineFocusTimeout()); } void Application::socketDisconnected() { - if (closing) { - DEBUG_LOG(("Application Error: socket disconnected before command response received, quiting..")); + if (_secondInstance) { + DEBUG_LOG(("Application Error: socket disconnected before command response received, quitting...")); return App::quit(); } } void Application::newInstanceConnected() { DEBUG_LOG(("Application Info: new local socket connected")); - for (QLocalSocket *client = server.nextPendingConnection(); client; client = server.nextPendingConnection()) { - clients.push_back(ClientSocket(client, QByteArray())); + for (QLocalSocket *client = _localServer.nextPendingConnection(); client; client = _localServer.nextPendingConnection()) { + _localClients.push_back(LocalClient(client, QByteArray())); connect(client, SIGNAL(readyRead()), this, SLOT(readClients())); connect(client, SIGNAL(disconnected()), this, SLOT(removeClients())); } @@ -797,16 +252,16 @@ void Application::newInstanceConnected() { void Application::readClients() { QString startUrl; QStringList toSend; - for (ClientSockets::iterator i = clients.begin(), e = clients.end(); i != e; ++i) { + for (LocalClients::iterator i = _localClients.begin(), e = _localClients.end(); i != e; ++i) { i->second.append(i->first->readAll()); if (i->second.size()) { - QString cmds(QString::fromLocal8Bit(i->second)); + QString cmds(QString::fromLatin1(i->second)); int32 from = 0, l = cmds.length(); for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; to = (from < l) ? cmds.indexOf(QChar(';'), from) : -1) { QStringRef cmd(&cmds, from, to - from); if (cmd.startsWith(qsl("CMD:"))) { - execExternal(cmds.mid(from + 4, to - from - 4)); - QByteArray response(qsl("RES:%1;").arg(QCoreApplication::applicationPid()).toUtf8()); + Sandbox::execExternal(cmds.mid(from + 4, to - from - 4)); + QByteArray response(qsl("RES:%1;").arg(QCoreApplication::applicationPid()).toLatin1()); i->first->write(response.data(), response.size()); } else if (cmd.startsWith(qsl("SEND:"))) { if (cSendPaths().isEmpty()) { @@ -846,65 +301,764 @@ void Application::readClients() { } void Application::removeClients() { - DEBUG_LOG(("Application Info: remove clients slot called, clients %1").arg(clients.size())); - for (ClientSockets::iterator i = clients.begin(), e = clients.end(); i != e;) { + DEBUG_LOG(("Application Info: remove clients slot called, clients %1").arg(_localClients.size())); + for (LocalClients::iterator i = _localClients.begin(), e = _localClients.end(); i != e;) { if (i->first->state() != QLocalSocket::ConnectedState) { DEBUG_LOG(("Application Info: removing client")); - i = clients.erase(i); - e = clients.end(); + i = _localClients.erase(i); + e = _localClients.end(); } else { ++i; } } } -void Application::execExternal(const QString &cmd) { - DEBUG_LOG(("Application Info: executing external command '%1'").arg(cmd)); - if (cmd == "show") { - window->activate(); +void Application::startApplication() { + if (App::quitting()) { + quit(); } } void Application::closeApplication() { - // close server - server.close(); - for (ClientSockets::iterator i = clients.begin(), e = clients.end(); i != e; ++i) { + if (App::launchState() == App::QuitProcessed) return; + App::setLaunchState(App::QuitProcessed); + + delete AppObject; + AppObject = 0; + + Sandbox::finish(); + + _localServer.close(); + for (LocalClients::iterator i = _localClients.begin(), e = _localClients.end(); i != e; ++i) { disconnect(i->first, SIGNAL(disconnected()), this, SLOT(removeClients())); i->first->close(); } - clients.clear(); + _localClients.clear(); - MTP::stop(); + _localSocket.close(); + +#ifndef TDESKTOP_DISABLE_AUTOUPDATE + delete _updateReply; + _updateReply = 0; + if (_updateChecker) _updateChecker->deleteLater(); + _updateChecker = 0; + if (_updateThread) _updateThread->quit(); + _updateThread = 0; +#endif } -Application::~Application() { - App::setQuiting(); +#ifndef TDESKTOP_DISABLE_AUTOUPDATE +void Application::updateCheck() { + startUpdateCheck(false); +} - window->setParent(0); +void Application::updateGotCurrent() { + if (!_updateReply || _updateThread) return; + cSetLastUpdateCheck(unixtime()); + QRegularExpressionMatch m = QRegularExpression(qsl("^\\s*(\\d+)\\s*:\\s*([\\x21-\\x7f]+)\\s*$")).match(QString::fromLatin1(_updateReply->readAll())); + if (m.hasMatch()) { + uint64 currentVersion = m.captured(1).toULongLong(); + QString url = m.captured(2); + bool betaVersion = false; + if (url.startsWith(qstr("beta_"))) { + betaVersion = true; + url = url.mid(5) + '_' + countBetaVersionSignature(currentVersion); + } + if ((!betaVersion || cBetaVersion()) && currentVersion > (betaVersion ? cBetaVersion() : uint64(AppVersion))) { + _updateThread = new QThread(); + connect(_updateThread, SIGNAL(finished()), _updateThread, SLOT(deleteLater())); + _updateChecker = new UpdateChecker(_updateThread, url); + _updateThread->start(); + } + } + if (_updateReply) _updateReply->deleteLater(); + _updateReply = 0; + if (!_updateThread) { + QDir updates(cWorkingDir() + "tupdates"); + if (updates.exists()) { + QFileInfoList list = updates.entryInfoList(QDir::Files); + for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) { + if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) { + QFile(i->absoluteFilePath()).remove(); + } + } + } + emit updateLatest(); + } + startUpdateCheck(true); + Local::writeSettings(); +} + +void Application::updateFailedCurrent(QNetworkReply::NetworkError e) { + LOG(("App Error: could not get current version (update check): %1").arg(e)); + if (_updateReply) _updateReply->deleteLater(); + _updateReply = 0; + + emit updateFailed(); + startUpdateCheck(true); +} + +void Application::onUpdateReady() { + if (_updateChecker) { + _updateChecker->deleteLater(); + _updateChecker = 0; + } + _updateCheckTimer.stop(); + + cSetLastUpdateCheck(unixtime()); + Local::writeSettings(); +} + +void Application::onUpdateFailed() { + if (_updateChecker) { + _updateChecker->deleteLater(); + _updateChecker = 0; + if (_updateThread) _updateThread->quit(); + _updateThread = 0; + } + + cSetLastUpdateCheck(unixtime()); + Local::writeSettings(); +} + +Application::UpdatingState Application::updatingState() { + if (!_updateThread) return Application::UpdatingNone; + if (!_updateChecker) return Application::UpdatingReady; + return Application::UpdatingDownload; +} + +int32 Application::updatingSize() { + if (!_updateChecker) return 0; + return _updateChecker->size(); +} + +int32 Application::updatingReady() { + if (!_updateChecker) return 0; + return _updateChecker->ready(); +} + +void Application::stopUpdate() { + if (_updateReply) { + _updateReply->abort(); + _updateReply->deleteLater(); + _updateReply = 0; + } + if (_updateChecker) { + _updateChecker->deleteLater(); + _updateChecker = 0; + if (_updateThread) _updateThread->quit(); + _updateThread = 0; + } +} + +void Application::startUpdateCheck(bool forceWait) { + _updateCheckTimer.stop(); + if (_updateThread || _updateReply || !cAutoUpdate()) return; + + int32 constDelay = cBetaVersion() ? 600 : UpdateDelayConstPart, randDelay = cBetaVersion() ? 300 : UpdateDelayRandPart; + int32 updateInSecs = cLastUpdateCheck() + constDelay + int32(rand() % randDelay) - unixtime(); + bool sendRequest = (updateInSecs <= 0 || updateInSecs > (constDelay + randDelay)); + if (!sendRequest && !forceWait) { + QDir updates(cWorkingDir() + "tupdates"); + if (updates.exists()) { + QFileInfoList list = updates.entryInfoList(QDir::Files); + for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) { + if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) { + sendRequest = true; + } + } + } + } + if (cManyInstance() && !cDebug()) return; // only main instance is updating + + if (sendRequest) { + QUrl url(cUpdateURL()); + if (cBetaVersion()) { + url.setQuery(qsl("version=%1&beta=%2").arg(AppVersion).arg(cBetaVersion())); + } else if (cDevVersion()) { + url.setQuery(qsl("version=%1&dev=1").arg(AppVersion)); + } else { + url.setQuery(qsl("version=%1").arg(AppVersion)); + } + QString u = url.toString(); + QNetworkRequest checkVersion(url); + if (_updateReply) _updateReply->deleteLater(); + + App::setProxySettings(_updateManager); + _updateReply = _updateManager.get(checkVersion); + connect(_updateReply, SIGNAL(finished()), this, SLOT(updateGotCurrent())); + connect(_updateReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(updateFailedCurrent(QNetworkReply::NetworkError))); + emit updateChecking(); + } else { + _updateCheckTimer.start((updateInSecs + 5) * 1000); + } +} + +#endif + +inline Application *application() { + return qobject_cast(QApplication::instance()); +} + +namespace Sandbox { + + QRect availableGeometry() { + if (Application *a = application()) { + return a->desktop()->availableGeometry(); + } + return QDesktopWidget().availableGeometry(); + } + + QRect screenGeometry(const QPoint &p) { + if (Application *a = application()) { + return a->desktop()->screenGeometry(p); + } + return QDesktopWidget().screenGeometry(p); + } + + void setActiveWindow(QWidget *window) { + if (Application *a = application()) { + a->setActiveWindow(window); + } + } + + bool isSavingSession() { + if (Application *a = application()) { + return a->isSavingSession(); + } + return false; + } + + void installEventFilter(QObject *filter) { + if (Application *a = application()) { + a->installEventFilter(filter); + } + } + + void execExternal(const QString &cmd) { + DEBUG_LOG(("Application Info: executing external command '%1'").arg(cmd)); + if (cmd == "show") { + if (App::wnd()) { + App::wnd()->activate(); + } else if (PreLaunchWindow::instance()) { + PreLaunchWindow::instance()->activate(); + } + } + } + +#ifndef TDESKTOP_DISABLE_AUTOUPDATE + + void startUpdateCheck() { + if (Application *a = application()) { + return a->startUpdateCheck(false); + } + } + + void stopUpdate() { + if (Application *a = application()) { + return a->stopUpdate(); + } + } + + Application::UpdatingState updatingState() { + if (Application *a = application()) { + return a->updatingState(); + } + return Application::UpdatingNone; + } + + int32 updatingSize() { + if (Application *a = application()) { + return a->updatingSize(); + } + return 0; + } + + int32 updatingReady() { + if (Application *a = application()) { + return a->updatingReady(); + } + return 0; + } + + void updateChecking() { + if (Application *a = application()) { + emit a->updateChecking(); + } + } + + void updateLatest() { + if (Application *a = application()) { + emit a->updateLatest(); + } + } + + void updateProgress(qint64 ready, qint64 total) { + if (Application *a = application()) { + emit a->updateProgress(ready, total); + } + } + + void updateFailed() { + if (Application *a = application()) { + emit a->updateFailed(); + } + } + + void updateReady() { + if (Application *a = application()) { + emit a->updateReady(); + } + } + +#endif + + void connect(const char *signal, QObject *object, const char *method) { + if (Application *a = application()) { + a->connect(a, signal, object, method); + } + } + + void launch() { + t_assert(application() != 0); + + float64 dpi = Application::primaryScreen()->logicalDotsPerInch(); + if (dpi <= 108) { // 0-96-108 + cSetScreenScale(dbisOne); + } else if (dpi <= 132) { // 108-120-132 + cSetScreenScale(dbisOneAndQuarter); + } else if (dpi <= 168) { // 132-144-168 + cSetScreenScale(dbisOneAndHalf); + } else { // 168-192-inf + cSetScreenScale(dbisTwo); + } + + if (application()->devicePixelRatio() > 1) { + cSetRetina(true); + cSetRetinaFactor(application()->devicePixelRatio()); + cSetIntRetinaFactor(int32(cRetinaFactor())); + cSetConfigScale(dbisOne); + cSetRealScale(dbisOne); + } + + new AppClass(); + } + +} + +AppClass::AppClass() : QObject() +, _lastActionTime(0) +, _window(0) +, _uploader(0) +, _translator(0) { + AppObject = this; + + Fonts::start(); + + ThirdParty::start(); + Global::start(); + Local::start(); + if (Local::oldSettingsVersion() < AppVersion) { + psNewVersion(); + } + + if (cLaunchMode() == LaunchModeAutoStart && !cAutoStart()) { + psAutoStart(false, true); + application()->quit(); + return; + } + + if (cRetina()) { + cSetConfigScale(dbisOne); + cSetRealScale(dbisOne); + } + + if (cLang() < languageTest) { + cSetLang(Sandbox::LangSystem()); + } + if (cLang() == languageTest) { + if (QFileInfo(cLangFile()).exists()) { + LangLoaderPlain loader(cLangFile()); + cSetLangErrors(loader.errors()); + if (!cLangErrors().isEmpty()) { + LOG(("Lang load errors: %1").arg(cLangErrors())); + } else if (!loader.warnings().isEmpty()) { + LOG(("Lang load warnings: %1").arg(loader.warnings())); + } + } else { + cSetLang(languageDefault); + } + } else if (cLang() > languageDefault && cLang() < languageCount) { + LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[cLang()].c_str() + qsl(".strings")); + if (!loader.errors().isEmpty()) { + LOG(("Lang load errors: %1").arg(loader.errors())); + } else if (!loader.warnings().isEmpty()) { + LOG(("Lang load warnings: %1").arg(loader.warnings())); + } + } + + application()->installTranslator(_translator = new Translator()); + + style::startManager(); + anim::startManager(); + historyInit(); + + DEBUG_LOG(("Application Info: inited...")); + + application()->installNativeEventFilter(psNativeEventFilter()); + + cChangeTimeFormat(QLocale::system().timeFormat(QLocale::ShortFormat)); + + connect(&_mtpUnpauseTimer, SIGNAL(timeout()), this, SLOT(doMtpUnpause())); + + connect(&killDownloadSessionsTimer, SIGNAL(timeout()), this, SLOT(killDownloadSessions())); + + DEBUG_LOG(("Application Info: starting app...")); + + QMimeDatabase().mimeTypeForName(qsl("text/plain")); // create mime database + + _window = new Window(); + _window->createWinId(); + _window->init(); + + Sandbox::connect(SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(onAppStateChanged(Qt::ApplicationState))); + + DEBUG_LOG(("Application Info: window created...")); + + Shortcuts::start(); + + initImageLinkManager(); + App::initMedia(); + + Local::ReadMapState state = Local::readMap(QByteArray()); + if (state == Local::ReadMapPassNeeded) { + cSetHasPasscode(true); + DEBUG_LOG(("Application Info: passcode needed...")); + } else { + DEBUG_LOG(("Application Info: local map read...")); + MTP::start(); + } + + MTP::setStateChangedHandler(mtpStateChanged); + MTP::setSessionResetHandler(mtpSessionReset); + + DEBUG_LOG(("Application Info: MTP started...")); + + DEBUG_LOG(("Application Info: showing.")); + if (state == Local::ReadMapPassNeeded) { + _window->setupPasscode(false); + } else { + if (MTP::authedId()) { + _window->setupMain(false); + } else { + _window->setupIntro(false); + } + } + _window->firstShow(); + + if (cStartToSettings()) { + _window->showSettings(); + } + +#ifndef TDESKTOP_DISABLE_NETWORK_PROXY + QNetworkProxyFactory::setUseSystemConfiguration(true); +#endif + + if (state != Local::ReadMapPassNeeded) { + checkMapVersion(); + } + + _window->updateIsActive(Global::OnlineFocusTimeout()); + + if (!Shortcuts::errors().isEmpty()) { + const QStringList &errors(Shortcuts::errors()); + for (QStringList::const_iterator i = errors.cbegin(), e = errors.cend(); i != e; ++i) { + LOG(("Shortcuts Error: %1").arg(*i)); + } + } +} + +void AppClass::regPhotoUpdate(const PeerId &peer, const FullMsgId &msgId) { + photoUpdates.insert(msgId, peer); +} + +void AppClass::clearPhotoUpdates() { + photoUpdates.clear(); +} + +bool AppClass::isPhotoUpdating(const PeerId &peer) { + for (QMap::iterator i = photoUpdates.begin(), e = photoUpdates.end(); i != e; ++i) { + if (i.value() == peer) { + return true; + } + } + return false; +} + +void AppClass::cancelPhotoUpdate(const PeerId &peer) { + for (QMap::iterator i = photoUpdates.begin(), e = photoUpdates.end(); i != e;) { + if (i.value() == peer) { + i = photoUpdates.erase(i); + } else { + ++i; + } + } +} + +void AppClass::mtpPause() { + MTP::pause(); + _mtpUnpauseTimer.start(st::slideDuration * 2); +} + +void AppClass::mtpUnpause() { + _mtpUnpauseTimer.start(1); +} + +void AppClass::doMtpUnpause() { + MTP::unpause(); +} + +void AppClass::selfPhotoCleared(const MTPUserProfilePhoto &result) { + if (!App::self()) return; + App::self()->setPhoto(result); + emit peerPhotoDone(App::self()->id); +} + +void AppClass::chatPhotoCleared(PeerId peer, const MTPUpdates &updates) { + if (App::main()) { + App::main()->sentUpdatesReceived(updates); + } + cancelPhotoUpdate(peer); + emit peerPhotoDone(peer); +} + +void AppClass::selfPhotoDone(const MTPphotos_Photo &result) { + if (!App::self()) return; + const MTPDphotos_photo &photo(result.c_photos_photo()); + App::feedPhoto(photo.vphoto); + App::feedUsers(photo.vusers); + cancelPhotoUpdate(App::self()->id); + emit peerPhotoDone(App::self()->id); +} + +void AppClass::chatPhotoDone(PeerId peer, const MTPUpdates &updates) { + if (App::main()) { + App::main()->sentUpdatesReceived(updates); + } + cancelPhotoUpdate(peer); + emit peerPhotoDone(peer); +} + +bool AppClass::peerPhotoFail(PeerId peer, const RPCError &error) { + if (mtpIsFlood(error)) return false; + + LOG(("Application Error: update photo failed %1: %2").arg(error.type()).arg(error.description())); + cancelPhotoUpdate(peer); + emit peerPhotoFail(peer); + return true; +} + +void AppClass::peerClearPhoto(PeerId id) { + if (MTP::authedId() && peerToUser(id) == MTP::authedId()) { + MTP::send(MTPphotos_UpdateProfilePhoto(MTP_inputPhotoEmpty(), MTP_inputPhotoCropAuto()), rpcDone(&AppClass::selfPhotoCleared), rpcFail(&AppClass::peerPhotoFail, id)); + } else if (peerIsChat(id)) { + MTP::send(MTPmessages_EditChatPhoto(peerToBareMTPInt(id), MTP_inputChatPhotoEmpty()), rpcDone(&AppClass::chatPhotoCleared, id), rpcFail(&AppClass::peerPhotoFail, id)); + } else if (peerIsChannel(id)) { + if (ChannelData *channel = App::channelLoaded(id)) { + MTP::send(MTPchannels_EditPhoto(channel->inputChannel, MTP_inputChatPhotoEmpty()), rpcDone(&AppClass::chatPhotoCleared, id), rpcFail(&AppClass::peerPhotoFail, id)); + } + } +} + +void AppClass::killDownloadSessionsStart(int32 dc) { + if (killDownloadSessionTimes.constFind(dc) == killDownloadSessionTimes.cend()) { + killDownloadSessionTimes.insert(dc, getms() + MTPAckSendWaiting + MTPKillFileSessionTimeout); + } + if (!killDownloadSessionsTimer.isActive()) { + killDownloadSessionsTimer.start(MTPAckSendWaiting + MTPKillFileSessionTimeout + 5); + } +} + +void AppClass::killDownloadSessionsStop(int32 dc) { + killDownloadSessionTimes.remove(dc); + if (killDownloadSessionTimes.isEmpty() && killDownloadSessionsTimer.isActive()) { + killDownloadSessionsTimer.stop(); + } +} + +void AppClass::checkLocalTime() { + if (App::main()) App::main()->checkLastUpdate(checkms()); +} + +void AppClass::onAppStateChanged(Qt::ApplicationState state) { + checkLocalTime(); + if (_window) { + _window->updateIsActive((state == Qt::ApplicationActive) ? Global::OnlineFocusTimeout() : Global::OfflineBlurTimeout()); + } + if (state != Qt::ApplicationActive) { + PopupTooltip::Hide(); + } +} + +void AppClass::call_handleHistoryUpdate() { + Notify::handlePendingHistoryUpdate(); +} + +void AppClass::killDownloadSessions() { + uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout; + for (QMap::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) { + if (i.value() <= ms) { + for (int j = 0; j < MTPDownloadSessionsCount; ++j) { + MTP::stopSession(MTP::dldDcId(i.key(), j)); + } + i = killDownloadSessionTimes.erase(i); + } else { + if (i.value() - ms < left) { + left = i.value() - ms; + } + ++i; + } + } + if (!killDownloadSessionTimes.isEmpty()) { + killDownloadSessionsTimer.start(left); + } +} + +void AppClass::photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file) { + if (!App::self()) return; + + QMap::iterator i = photoUpdates.find(msgId); + if (i != photoUpdates.end()) { + PeerId id = i.value(); + if (MTP::authedId() && peerToUser(id) == MTP::authedId()) { + MTP::send(MTPphotos_UploadProfilePhoto(file, MTP_string(""), MTP_inputGeoPointEmpty(), MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100))), rpcDone(&AppClass::selfPhotoDone), rpcFail(&AppClass::peerPhotoFail, id)); + } else if (peerIsChat(id)) { + History *hist = App::history(id); + hist->sendRequestId = MTP::send(MTPmessages_EditChatPhoto(hist->peer->asChat()->inputChat, MTP_inputChatUploadedPhoto(file, MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100)))), rpcDone(&AppClass::chatPhotoDone, id), rpcFail(&AppClass::peerPhotoFail, id), 0, 0, hist->sendRequestId); + } else if (peerIsChannel(id)) { + History *hist = App::history(id); + hist->sendRequestId = MTP::send(MTPchannels_EditPhoto(hist->peer->asChannel()->inputChannel, MTP_inputChatUploadedPhoto(file, MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100)))), rpcDone(&AppClass::chatPhotoDone, id), rpcFail(&AppClass::peerPhotoFail, id), 0, 0, hist->sendRequestId); + } + } +} + +void AppClass::onSwitchDebugMode() { + if (cDebug()) { + QFile(cWorkingDir() + qsl("tdata/withdebug")).remove(); + cSetDebug(false); + cSetRestarting(true); + cSetRestartingToSettings(true); + App::quit(); + } else { + cSetDebug(true); + DEBUG_LOG(("Debug logs started.")); + QFile f(cWorkingDir() + qsl("tdata/withdebug")); + if (f.open(QIODevice::WriteOnly)) { + f.write("1"); + f.close(); + } + Ui::hideLayer(); + } +} + +void AppClass::onSwitchTestMode() { + if (cTestMode()) { + QFile(cWorkingDir() + qsl("tdata/withtestmode")).remove(); + cSetTestMode(false); + } else { + QFile f(cWorkingDir() + qsl("tdata/withtestmode")); + if (f.open(QIODevice::WriteOnly)) { + f.write("1"); + f.close(); + } + cSetTestMode(true); + } + cSetRestarting(true); + cSetRestartingToSettings(true); + App::quit(); +} + +FileUploader *AppClass::uploader() { + if (!_uploader && !App::quitting()) _uploader = new FileUploader(); + return _uploader; +} + +void AppClass::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) { + PreparedPhotoThumbs photoThumbs; + QVector photoSizes; + + QPixmap thumb = QPixmap::fromImage(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); + photoThumbs.insert('a', thumb); + photoSizes.push_back(MTP_photoSize(MTP_string("a"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0))); + + QPixmap medium = QPixmap::fromImage(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); + photoThumbs.insert('b', medium); + photoSizes.push_back(MTP_photoSize(MTP_string("b"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0))); + + QPixmap full = QPixmap::fromImage(tosend, Qt::ColorOnly); + photoThumbs.insert('c', full); + photoSizes.push_back(MTP_photoSize(MTP_string("c"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0))); + + QByteArray jpeg; + QBuffer jpegBuffer(&jpeg); + full.save(&jpegBuffer, "JPG", 87); + + PhotoId id = rand_value(); + + MTPPhoto photo(MTP_photo(MTP_long(id), MTP_long(0), MTP_int(unixtime()), MTP_vector(photoSizes))); + + QString file, filename; + int32 filesize = 0; + QByteArray data; + + ReadyLocalMedia ready(PreparePhoto, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, false, false, 0); + + connect(App::uploader(), SIGNAL(photoReady(const FullMsgId&,bool,const MTPInputFile&)), App::app(), SLOT(photoUpdated(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection); + + FullMsgId newId(peerToChannel(peerId), clientMsgId()); + App::app()->regPhotoUpdate(peerId, newId); + App::uploader()->uploadMedia(newId, ready); +} + +void AppClass::checkMapVersion() { + if (Local::oldMapVersion() < AppVersion) { + if (Local::oldMapVersion()) { + QString versionFeatures; + if ((cDevVersion() || cBetaVersion()) && Local::oldMapVersion() < 9040) { +// versionFeatures = QString::fromUtf8("\xe2\x80\x94 Design improvements\n\xe2\x80\x94 Bug fixes and other minor improvements"); + versionFeatures = langNewVersionText(); + } else if (Local::oldMapVersion() < 9040) { + versionFeatures = langNewVersionText(); + } else { + versionFeatures = lang(lng_new_version_minor).trimmed(); + } + if (!versionFeatures.isEmpty()) { + versionFeatures = lng_new_version_wrap(lt_version, QString::fromStdWString(AppVersionStr), lt_changes, versionFeatures, lt_link, qsl("https://desktop.telegram.org/#changelog")); + _window->serviceNotification(versionFeatures); + } + } + } +} + +AppClass::~AppClass() { + Shortcuts::finish(); + + if (Window *w = _window) { + _window = 0; + delete w; + } anim::stopManager(); - socket.close(); - closeApplication(); stopWebLoadManager(); App::deinitMedia(); deinitImageLinkManager(); - mainApp = 0; - delete ::uploader; + MTP::finish(); - #ifndef TDESKTOP_DISABLE_AUTOUPDATE - delete updateReply; - updateReply = 0; - if (updateDownloader) updateDownloader->deleteLater(); - updateDownloader = 0; - if (updateThread) updateThread->quit(); - updateThread = 0; - #endif - - Window *w = window; - window = 0; - delete w; + AppObject = 0; + deleteAndMark(_uploader); + deleteAndMark(_translator); delete cChatBackground(); cSetChatBackground(0); @@ -914,37 +1068,19 @@ Application::~Application() { style::stopManager(); - delete _translator; + Local::finish(); + Global::finish(); + ThirdParty::finish(); } -Application *Application::app() { - return mainApp; +AppClass *AppClass::app() { + return AppObject; } -Window *Application::wnd() { - return mainApp ? mainApp->window : 0; +Window *AppClass::wnd() { + return AppObject ? AppObject->_window : 0; } -QString Application::language() { - if (!lng.length()) { - lng = psCurrentLanguage(); - } - if (!lng.length()) { - lng = "en"; - } - return lng; -} - -int32 Application::languageId() { - QByteArray l = language().toLatin1(); - for (int32 i = 0; i < languageCount; ++i) { - if (l.at(0) == LanguageCodes[i][0] && l.at(1) == LanguageCodes[i][1]) { - return i; - } - } - return languageDefault; -} - -MainWidget *Application::main() { - return mainApp ? mainApp->window->mainWidget() : 0; +MainWidget *AppClass::main() { + return (AppObject && AppObject->_window) ? AppObject->_window->mainWidget() : 0; } diff --git a/Telegram/SourceFiles/application.h b/Telegram/SourceFiles/application.h index 21866e49fa..3e0a37e565 100644 --- a/Telegram/SourceFiles/application.h +++ b/Telegram/SourceFiles/application.h @@ -16,35 +16,57 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include -#include -#include - #include "window.h" #include "pspecific.h" -class MainWidget; -class FileUploader; -class Translator; -class UpdateDownloader; - -class Application : public PsApplication, public RPCSender { +class UpdateChecker; +class Application : public QApplication { Q_OBJECT public: Application(int &argc, char **argv); - ~Application(); - - static Application *app(); - static Window *wnd(); - static QString language(); - static int32 languageId(); - static MainWidget *main(); + +// Single instance application +public slots: + + void socketConnected(); + void socketError(QLocalSocket::LocalSocketError e); + void socketDisconnected(); + void socketWritten(qint64 bytes); + void socketReading(); + void newInstanceConnected(); + + void readClients(); + void removeClients(); + + void startApplication(); // will be done in exec() + void closeApplication(); // will be done in aboutToQuit() + +private: + + typedef QPair LocalClient; + typedef QList LocalClients; + + QString _localServerName, _localSocketReadData; + QLocalServer _localServer; + QLocalSocket _localSocket; + LocalClients _localClients; + bool _secondInstance; + + void singleInstanceChecked(); + +#ifndef TDESKTOP_DISABLE_AUTOUPDATE + +// Autoupdating +public: + + void startUpdateCheck(bool forceWait); + void stopUpdate(); enum UpdatingState { UpdatingNone, @@ -52,11 +74,87 @@ public: UpdatingReady, }; UpdatingState updatingState(); - #ifndef TDESKTOP_DISABLE_AUTOUPDATE int32 updatingSize(); int32 updatingReady(); + +signals: + + void updateChecking(); + void updateLatest(); + void updateProgress(qint64 ready, qint64 total); + void updateReady(); + void updateFailed(); + +public slots: + + void updateCheck(); + + void updateGotCurrent(); + void updateFailedCurrent(QNetworkReply::NetworkError e); + + void onUpdateReady(); + void onUpdateFailed(); + +private: + + SingleTimer _updateCheckTimer; + QNetworkReply *_updateReply; + QNetworkAccessManager _updateManager; + QThread *_updateThread; + UpdateChecker *_updateChecker; + +#endif +}; + +namespace Sandbox { + + QRect availableGeometry(); + QRect screenGeometry(const QPoint &p); + void setActiveWindow(QWidget *window); + bool isSavingSession(); + + void installEventFilter(QObject *filter); + + void execExternal(const QString &cmd); + +#ifndef TDESKTOP_DISABLE_AUTOUPDATE + + void startUpdateCheck(); void stopUpdate(); - #endif + + Application::UpdatingState updatingState(); + int32 updatingSize(); + int32 updatingReady(); + + void updateChecking(); + void updateLatest(); + void updateProgress(qint64 ready, qint64 total); + void updateFailed(); + void updateReady(); + +#endif + + void connect(const char *signal, QObject *object, const char *method); + + void launch(); + +} + +class MainWidget; +class FileUploader; +class Translator; + +class AppClass : public QObject, public RPCSender { + Q_OBJECT + +public: + + AppClass(); + ~AppClass(); + + static AppClass *app(); + static Window *wnd(); + static MainWidget *main(); FileUploader *uploader(); void uploadProfilePhoto(const QImage &tosend, const PeerId &peerId); @@ -85,14 +183,6 @@ public: signals: - #ifndef TDESKTOP_DISABLE_AUTOUPDATE - void updateChecking(); - void updateLatest(); - void updateDownloading(qint64 ready, qint64 total); - void updateReady(); - void updateFailed(); - #endif - void peerPhotoDone(PeerId peer); void peerPhotoFail(PeerId peer); @@ -100,31 +190,9 @@ signals: public slots: - #ifndef TDESKTOP_DISABLE_AUTOUPDATE - void startUpdateCheck(bool forceWait = false); - #endif - void socketConnected(); - void socketError(QLocalSocket::LocalSocketError e); - void socketDisconnected(); - void socketWritten(qint64 bytes); - void socketReading(); - void newInstanceConnected(); - void closeApplication(); - void doMtpUnpause(); - void readClients(); - void removeClients(); - - #ifndef TDESKTOP_DISABLE_AUTOUPDATE - void updateGotCurrent(); - void updateFailedCurrent(QNetworkReply::NetworkError e); - - void onUpdateReady(); - void onUpdateFailed(); - #endif - - void photoUpdated(const FullMsgId &msgId, const MTPInputFile &file); + void photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file); void onSwitchDebugMode(); void onSwitchTestMode(); @@ -132,6 +200,8 @@ public slots: void killDownloadSessions(); void onAppStateChanged(Qt::ApplicationState state); + void call_handleHistoryUpdate(); + private: QMap photoUpdates; @@ -139,35 +209,12 @@ private: QMap killDownloadSessionTimes; SingleTimer killDownloadSessionsTimer; - void startApp(); + uint64 _lastActionTime; - typedef QPair ClientSocket; - typedef QVector ClientSockets; - - QString serverName; - QLocalSocket socket; - QString socketRead; - QLocalServer server; - ClientSockets clients; - bool closing; - - uint64 lastActionTime; - - void execExternal(const QString &cmd); - - Window *window; - - #ifndef TDESKTOP_DISABLE_AUTOUPDATE - mtpRequestId updateRequestId; - QNetworkAccessManager updateManager; - QNetworkReply *updateReply; - SingleTimer updateCheckTimer; - QThread *updateThread; - UpdateDownloader *updateDownloader; - #endif + Window *_window; + FileUploader *_uploader; + Translator *_translator; SingleTimer _mtpUnpauseTimer; - Translator *_translator; - }; diff --git a/Telegram/SourceFiles/art/sprite.png b/Telegram/SourceFiles/art/sprite.png index ce32997180..d919c9f3ac 100644 Binary files a/Telegram/SourceFiles/art/sprite.png and b/Telegram/SourceFiles/art/sprite.png differ diff --git a/Telegram/SourceFiles/art/sprite_200x.png b/Telegram/SourceFiles/art/sprite_200x.png index 16b454f057..0ceeb20dd9 100644 Binary files a/Telegram/SourceFiles/art/sprite_200x.png and b/Telegram/SourceFiles/art/sprite_200x.png differ diff --git a/Telegram/SourceFiles/audio.cpp b/Telegram/SourceFiles/audio.cpp index 6a5be7b68d..49a536ed13 100644 --- a/Telegram/SourceFiles/audio.cpp +++ b/Telegram/SourceFiles/audio.cpp @@ -16,9 +16,10 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" + #include "audio.h" #include @@ -27,10 +28,13 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #define AL_ALEXT_PROTOTYPES #include -#ifdef Q_OS_MAC - extern "C" { +#include +#include +#include +#include +#ifdef Q_OS_MAC #include #undef iconv_open @@ -46,10 +50,9 @@ size_t iconv (iconv_t cd, char* * inbuf, size_t *inbytesleft, char* * outbuf, s int iconv_close (iconv_t cd) { return libiconv_close(cd); } +#endif // Q_OS_MAC -} - -#endif +} // extern "C" namespace { ALCdevice *audioDevice = 0; @@ -96,12 +99,8 @@ bool _checkALError() { Q_DECLARE_METATYPE(AudioMsgId); Q_DECLARE_METATYPE(SongMsgId); +Q_DECLARE_METATYPE(VoiceWaveform); void audioInit() { - if (!audioDevice) { - av_register_all(); - avcodec_register_all(); - } - if (!capture) { capture = new AudioCapture(); cSetHasAudioCapture(capture->check()); @@ -114,7 +113,7 @@ void audioInit() { LOG(("Audio Error: default sound device not present.")); return; } - + ALCint attributes[] = { ALC_STEREO_SOURCES, 8, 0 }; audioContext = alcCreateContext(audioDevice, attributes); alcMakeContextCurrent(audioContext); @@ -211,6 +210,7 @@ void audioInit() { qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); player = new AudioPlayer(); alcDevicePauseSOFT(audioDevice); @@ -227,12 +227,15 @@ void audioPlayNotify() { emit audioPlayer()->faderOnTimer(); } +// can be called at any moment when audio error void audioFinish() { if (player) { delete player; + player = nullptr; } if (capture) { delete capture; + capture = nullptr; } alSourceStop(notifySource); @@ -246,14 +249,14 @@ void audioFinish() { } if (audioContext) { - alcMakeContextCurrent(NULL); + alcMakeContextCurrent(nullptr); alcDestroyContext(audioContext); - audioContext = 0; + audioContext = nullptr; } if (audioDevice) { alcCloseDevice(audioDevice); - audioDevice = 0; + audioDevice = nullptr; } cSetHasAudioCapture(false); @@ -373,8 +376,8 @@ void AudioPlayer::onStopped(const SongMsgId &song) { bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) { Msg *data = 0; switch (type) { - case OverviewAudios: data = &_audioData[_audioCurrent]; break; - case OverviewDocuments: data = &_songData[_songCurrent]; break; + case OverviewVoiceFiles: data = &_audioData[_audioCurrent]; break; + case OverviewFiles: data = &_songData[_songCurrent]; break; } if (!data) return false; @@ -387,8 +390,8 @@ bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) { if (!_checkALError()) { setStoppedState(data, AudioPlayerStoppedAtError); switch (type) { - case OverviewAudios: onError(_audioData[_audioCurrent].audio); break; - case OverviewDocuments: onError(_songData[_songCurrent].song); break; + case OverviewVoiceFiles: onError(_audioData[_audioCurrent].audio); break; + case OverviewFiles: onError(_songData[_songCurrent].song); break; } return false; } @@ -400,8 +403,8 @@ bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) { bool AudioPlayer::fadedStop(MediaOverviewType type, bool *fadedStart) { Msg *current = 0; switch (type) { - case OverviewAudios: current = &_audioData[_audioCurrent]; break; - case OverviewDocuments: current = &_songData[_songCurrent]; break; + case OverviewVoiceFiles: current = &_audioData[_audioCurrent]; break; + case OverviewFiles: current = &_songData[_songCurrent]; break; } if (!current) return false; @@ -433,7 +436,7 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) { bool fadedStart = false; AudioMsg *current = &_audioData[_audioCurrent]; if (current->audio != audio) { - if (fadedStop(OverviewAudios, &fadedStart)) { + if (fadedStop(OverviewVoiceFiles, &fadedStart)) { stopped = current->audio; } if (current->audio) { @@ -477,7 +480,7 @@ void AudioPlayer::play(const SongMsgId &song, int64 position) { bool fadedStart = false; SongMsg *current = &_songData[_songCurrent]; if (current->song != song) { - if (fadedStop(OverviewDocuments, &fadedStart)) { + if (fadedStop(OverviewFiles, &fadedStart)) { stopped = current->song; } if (current->song) { @@ -503,7 +506,7 @@ void AudioPlayer::play(const SongMsgId &song, int64 position) { if (current->file.isEmpty() && current->data.isEmpty()) { setStoppedState(current); if (!song.song->loading()) { - DocumentOpenLink::doOpen(song.song); + DocumentOpenClickHandler::doOpen(song.song); } } else { current->state = fadedStart ? AudioPlayerStarting : AudioPlayerPlaying; @@ -516,13 +519,13 @@ void AudioPlayer::play(const SongMsgId &song, int64 position) { bool AudioPlayer::checkCurrentALError(MediaOverviewType type) { if (_checkALError()) return true; - + switch (type) { - case OverviewAudios: + case OverviewVoiceFiles: setStoppedState(&_audioData[_audioCurrent], AudioPlayerStoppedAtError); onError(_audioData[_audioCurrent].audio); break; - case OverviewDocuments: + case OverviewFiles: setStoppedState(&_songData[_songCurrent], AudioPlayerStoppedAtError); onError(_songData[_songCurrent].song); break; @@ -536,11 +539,11 @@ void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) { Msg *current = 0; float64 suppressGain = 1.; switch (type) { - case OverviewAudios: + case OverviewVoiceFiles: current = &_audioData[_audioCurrent]; suppressGain = suppressAllGain; break; - case OverviewDocuments: + case OverviewFiles: current = &_songData[_songCurrent]; suppressGain = suppressSongGain * cSongVolume(); break; @@ -572,14 +575,14 @@ void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) { alSourcePlay(current->source); if (!checkCurrentALError(type)) return; } - if (type == OverviewAudios) emit suppressSong(); + if (type == OverviewVoiceFiles) emit suppressSong(); } break; case AudioPlayerStarting: case AudioPlayerResuming: case AudioPlayerPlaying: current->state = AudioPlayerPausing; updateCurrentStarted(type); - if (type == OverviewAudios) emit unsuppressSong(); + if (type == OverviewVoiceFiles) emit unsuppressSong(); break; case AudioPlayerFinishing: current->state = AudioPlayerPausing; break; } @@ -589,18 +592,18 @@ void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) { void AudioPlayer::seek(int64 position) { QMutexLocker lock(&playerMutex); - MediaOverviewType type = OverviewDocuments; + MediaOverviewType type = OverviewFiles; Msg *current = 0; float64 suppressGain = 1.; AudioMsgId audio; SongMsgId song; switch (type) { - case OverviewAudios: + case OverviewVoiceFiles: current = &_audioData[_audioCurrent]; audio = _audioData[_audioCurrent].audio; suppressGain = suppressAllGain; break; - case OverviewDocuments: + case OverviewFiles: current = &_songData[_songCurrent]; song = _songData[_songCurrent].song; suppressGain = suppressSongGain * cSongVolume(); @@ -634,7 +637,7 @@ void AudioPlayer::seek(int64 position) { case AudioPlayerPlaying: current->state = AudioPlayerPausing; updateCurrentStarted(type); - if (type == OverviewAudios) emit unsuppressSong(); + if (type == OverviewVoiceFiles) emit unsuppressSong(); break; case AudioPlayerFinishing: case AudioPlayerStopped: @@ -643,8 +646,8 @@ void AudioPlayer::seek(int64 position) { case AudioPlayerStoppedAtStart: lock.unlock(); switch (type) { - case OverviewAudios: if (audio) return play(audio, position); - case OverviewDocuments: if (song) return play(song, position); + case OverviewVoiceFiles: if (audio) return play(audio, position); + case OverviewFiles: if (song) return play(song, position); } } emit faderOnTimer(); @@ -652,7 +655,7 @@ void AudioPlayer::seek(int64 position) { void AudioPlayer::stop(MediaOverviewType type) { switch (type) { - case OverviewAudios: { + case OverviewVoiceFiles: { AudioMsgId current; { QMutexLocker lock(&playerMutex); @@ -662,7 +665,7 @@ void AudioPlayer::stop(MediaOverviewType type) { if (current) emit updated(current); } break; - case OverviewDocuments: { + case OverviewFiles: { SongMsgId current; { QMutexLocker lock(&playerMutex); @@ -759,8 +762,8 @@ void AudioPlayer::resumeDevice() { AudioCapture::AudioCapture() : _capture(new AudioCaptureInner(&_captureThread)) { connect(this, SIGNAL(captureOnStart()), _capture, SLOT(onStart())); connect(this, SIGNAL(captureOnStop(bool)), _capture, SLOT(onStop(bool))); - connect(_capture, SIGNAL(done(QByteArray,qint32)), this, SIGNAL(onDone(QByteArray,qint32))); - connect(_capture, SIGNAL(update(qint16,qint32)), this, SIGNAL(onUpdate(qint16,qint32))); + connect(_capture, SIGNAL(done(QByteArray,VoiceWaveform,qint32)), this, SIGNAL(onDone(QByteArray,VoiceWaveform,qint32))); + connect(_capture, SIGNAL(update(quint16,qint32)), this, SIGNAL(onUpdate(quint16,qint32))); connect(_capture, SIGNAL(error()), this, SIGNAL(onError())); connect(&_captureThread, SIGNAL(started()), _capture, SLOT(onInit())); connect(&_captureThread, SIGNAL(finished()), _capture, SLOT(deleteLater())); @@ -1091,7 +1094,7 @@ protected: QFile f; int32 dataPos; - + bool openFile() { if (data.isEmpty()) { if (f.isOpen()) f.close(); @@ -1114,19 +1117,18 @@ protected: }; -static const AVSampleFormat _toFormat = AV_SAMPLE_FMT_S16; -static const int64_t _toChannelLayout = AV_CH_LAYOUT_STEREO; -static const int32 _toChannels = 2; -class FFMpegLoader : public AudioPlayerLoader { +class AbstractFFMpegLoader : public AudioPlayerLoader { public: - FFMpegLoader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data), - freq(AudioVoiceMsgFrequency), fmt(AL_FORMAT_STEREO16), - sampleSize(2 * sizeof(short)), srcRate(AudioVoiceMsgFrequency), dstRate(AudioVoiceMsgFrequency), - maxResampleSamples(1024), dstSamplesData(0), len(0), - ioBuffer(0), ioContext(0), fmtContext(0), codec(0), codecContext(0), streamId(0), frame(0), swrContext(0), - _opened(false) { - frame = av_frame_alloc(); + AbstractFFMpegLoader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data) + , freq(AudioVoiceMsgFrequency) + , len(0) + , ioBuffer(0) + , ioContext(0) + , fmtContext(0) + , codec(0) + , streamId(0) + , _opened(false) { } bool open(qint64 position = 0) { @@ -1134,31 +1136,32 @@ public: return false; } + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + ioBuffer = (uchar*)av_malloc(AVBlockSize); if (data.isEmpty()) { - ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegLoader::_read_file, 0, &FFMpegLoader::_seek_file); + ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_file, 0, &AbstractFFMpegLoader::_seek_file); } else { - ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegLoader::_read_data, 0, &FFMpegLoader::_seek_data); + ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_data, 0, &AbstractFFMpegLoader::_seek_data); } fmtContext = avformat_alloc_context(); if (!fmtContext) { - LOG(("Audio Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(file.name()).arg(data.size())); + DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(file.name()).arg(data.size())); return false; } fmtContext->pb = ioContext; - int res = 0; - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) { ioBuffer = 0; - LOG(("Audio Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return false; } _opened = true; if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) { - LOG(("Audio Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return false; } @@ -1168,20 +1171,130 @@ public: return false; } - // Get a pointer to the codec context for the audio stream - codecContext = fmtContext->streams[streamId]->codec; - av_opt_set_int(codecContext, "refcounted_frames", 1, 0); - if ((res = avcodec_open2(codecContext, codec, 0)) < 0) { - LOG(("Audio Error: Unable to avcodec_open2 for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - - freq = codecContext->sample_rate; + freq = fmtContext->streams[streamId]->codec->sample_rate; if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) { len = (fmtContext->duration * freq) / AV_TIME_BASE; } else { len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den; } + + return true; + } + + int64 duration() { + return len; + } + + int32 frequency() { + return freq; + } + + ~AbstractFFMpegLoader() { + if (ioContext) av_free(ioContext); + if (_opened) { + avformat_close_input(&fmtContext); + } else if (ioBuffer) { + av_free(ioBuffer); + } + if (fmtContext) avformat_free_context(fmtContext); + } + +protected: + + int32 freq; + int64 len; + + uchar *ioBuffer; + AVIOContext *ioContext; + AVFormatContext *fmtContext; + AVCodec *codec; + int32 streamId; + + bool _opened; + +private: + + static int _read_data(void *opaque, uint8_t *buf, int buf_size) { + AbstractFFMpegLoader *l = reinterpret_cast(opaque); + + int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size)); + if (nbytes <= 0) { + return 0; + } + + memcpy(buf, l->data.constData() + l->dataPos, nbytes); + l->dataPos += nbytes; + return nbytes; + } + + static int64_t _seek_data(void *opaque, int64_t offset, int whence) { + AbstractFFMpegLoader *l = reinterpret_cast(opaque); + + int32 newPos = -1; + switch (whence) { + case SEEK_SET: newPos = offset; break; + case SEEK_CUR: newPos = l->dataPos + offset; break; + case SEEK_END: newPos = l->data.size() + offset; break; + } + if (newPos < 0 || newPos > l->data.size()) { + return -1; + } + l->dataPos = newPos; + return l->dataPos; + } + + static int _read_file(void *opaque, uint8_t *buf, int buf_size) { + AbstractFFMpegLoader *l = reinterpret_cast(opaque); + return int(l->f.read((char*)(buf), buf_size)); + } + + static int64_t _seek_file(void *opaque, int64_t offset, int whence) { + AbstractFFMpegLoader *l = reinterpret_cast(opaque); + + switch (whence) { + case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1; + case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1; + case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1; + } + return -1; + } +}; + +static const AVSampleFormat _toFormat = AV_SAMPLE_FMT_S16; +static const int64_t _toChannelLayout = AV_CH_LAYOUT_STEREO; +static const int32 _toChannels = 2; +class FFMpegLoader : public AbstractFFMpegLoader { +public: + + FFMpegLoader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) + , sampleSize(2 * sizeof(uint16)) + , fmt(AL_FORMAT_STEREO16) + , srcRate(AudioVoiceMsgFrequency) + , dstRate(AudioVoiceMsgFrequency) + , maxResampleSamples(1024) + , dstSamplesData(0) + , codecContext(0) + , frame(0) + , swrContext(0) { + frame = av_frame_alloc(); + } + + bool open(qint64 position = 0) { + if (!AbstractFFMpegLoader::open(position)) { + return false; + } + + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + + // Get a pointer to the codec context for the audio stream + av_opt_set_int(fmtContext->streams[streamId]->codec, "refcounted_frames", 1, 0); + if ((res = avcodec_open2(fmtContext->streams[streamId]->codec, codec, 0)) < 0) { + LOG(("Audio Error: Unable to avcodec_open2 for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + codecContext = fmtContext->streams[streamId]->codec; + uint64_t layout = codecContext->channel_layout; inputFormat = codecContext->sample_fmt; switch (layout) { @@ -1190,7 +1303,7 @@ public: case AV_SAMPLE_FMT_U8: case AV_SAMPLE_FMT_U8P: fmt = AL_FORMAT_MONO8; sampleSize = 1; break; case AV_SAMPLE_FMT_S16: - case AV_SAMPLE_FMT_S16P: fmt = AL_FORMAT_MONO16; sampleSize = 2; break; + case AV_SAMPLE_FMT_S16P: fmt = AL_FORMAT_MONO16; sampleSize = sizeof(uint16); break; default: sampleSize = -1; // convert needed break; @@ -1198,8 +1311,8 @@ public: break; case AV_CH_LAYOUT_STEREO: switch (inputFormat) { - case AV_SAMPLE_FMT_U8: fmt = AL_FORMAT_STEREO8; sampleSize = sizeof(short); break; - case AV_SAMPLE_FMT_S16: fmt = AL_FORMAT_STEREO16; sampleSize = 2 * sizeof(short); break; + case AV_SAMPLE_FMT_U8: fmt = AL_FORMAT_STEREO8; sampleSize = 2; break; + case AV_SAMPLE_FMT_S16: fmt = AL_FORMAT_STEREO16; sampleSize = 2 * sizeof(uint16); break; default: sampleSize = -1; // convert needed break; @@ -1261,14 +1374,6 @@ public: return true; } - int64 duration() { - return len; - } - - int32 frequency() { - return freq; - } - int32 format() { return fmt; } @@ -1331,7 +1436,6 @@ public: } ~FFMpegLoader() { - if (ioContext) av_free(ioContext); if (codecContext) avcodec_close(codecContext); if (swrContext) swr_free(&swrContext); if (dstSamplesData) { @@ -1340,80 +1444,25 @@ public: } av_freep(&dstSamplesData); } - if (_opened) { - avformat_close_input(&fmtContext); - } else if (ioBuffer) { - av_free(ioBuffer); - } - if (fmtContext) avformat_free_context(fmtContext); av_frame_free(&frame); } +protected: + int32 sampleSize; + private: - int32 freq, fmt; - int32 sampleSize, srcRate, dstRate, maxResampleSamples; + int32 fmt; + int32 srcRate, dstRate, maxResampleSamples; uint8_t **dstSamplesData; - int64 len; - uchar *ioBuffer; - AVIOContext *ioContext; - AVFormatContext *fmtContext; - AVCodec *codec; AVCodecContext *codecContext; AVPacket avpkt; - int32 streamId; AVSampleFormat inputFormat; AVFrame *frame; SwrContext *swrContext; - bool _opened; - - static int _read_data(void *opaque, uint8_t *buf, int buf_size) { - FFMpegLoader *l = reinterpret_cast(opaque); - - int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size)); - if (nbytes <= 0) { - return 0; - } - - memcpy(buf, l->data.constData() + l->dataPos, nbytes); - l->dataPos += nbytes; - return nbytes; - } - - static int64_t _seek_data(void *opaque, int64_t offset, int whence) { - FFMpegLoader *l = reinterpret_cast(opaque); - - int32 newPos = -1; - switch (whence) { - case SEEK_SET: newPos = offset; break; - case SEEK_CUR: newPos = l->dataPos + offset; break; - case SEEK_END: newPos = l->data.size() + offset; break; - } - if (newPos < 0 || newPos > l->data.size()) { - return -1; - } - l->dataPos = newPos; - return l->dataPos; - } - - static int _read_file(void *opaque, uint8_t *buf, int buf_size) { - FFMpegLoader *l = reinterpret_cast(opaque); - return int(l->f.read((char*)(buf), buf_size)); - } - - static int64_t _seek_file(void *opaque, int64_t offset, int whence) { - FFMpegLoader *l = reinterpret_cast(opaque); - - switch (whence) { - case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1; - case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1; - case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1; - } - return -1; - } }; AudioPlayerLoaders::AudioPlayerLoaders(QThread *thread) : _audioLoader(0), _songLoader(0) { @@ -1441,7 +1490,7 @@ void AudioPlayerLoaders::onStart(const AudioMsgId &audio, qint64 position) { voice->_audioData[voice->_audioCurrent].loading = true; } - loadData(OverviewAudios, static_cast(&audio), position); + loadData(OverviewVoiceFiles, static_cast(&audio), position); } void AudioPlayerLoaders::onStart(const SongMsgId &song, qint64 position) { @@ -1457,13 +1506,13 @@ void AudioPlayerLoaders::onStart(const SongMsgId &song, qint64 position) { voice->_songData[voice->_songCurrent].loading = true; } - loadData(OverviewDocuments, static_cast(&song), position); + loadData(OverviewFiles, static_cast(&song), position); } void AudioPlayerLoaders::clear(MediaOverviewType type) { switch (type) { - case OverviewAudios: clearAudio(); break; - case OverviewDocuments: clearSong(); break; + case OverviewVoiceFiles: clearAudio(); break; + case OverviewFiles: clearSong(); break; } } @@ -1474,8 +1523,8 @@ void AudioPlayerLoaders::setStoppedState(AudioPlayer::Msg *m, AudioPlayerState s void AudioPlayerLoaders::emitError(MediaOverviewType type) { switch (type) { - case OverviewAudios: emit error(clearAudio()); break; - case OverviewDocuments: emit error(clearSong()); break; + case OverviewVoiceFiles: emit error(clearAudio()); break; + case OverviewFiles: emit error(clearSong()); break; } } @@ -1496,11 +1545,11 @@ SongMsgId AudioPlayerLoaders::clearSong() { } void AudioPlayerLoaders::onLoad(const AudioMsgId &audio) { - loadData(OverviewAudios, static_cast(&audio), 0); + loadData(OverviewVoiceFiles, static_cast(&audio), 0); } void AudioPlayerLoaders::onLoad(const SongMsgId &song) { - loadData(OverviewDocuments, static_cast(&song), 0); + loadData(OverviewFiles, static_cast(&song), 0); } void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId, qint64 position) { @@ -1613,8 +1662,8 @@ void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId, qin audioPlayer()->resumeDevice(); switch (type) { - case OverviewAudios: alSourcef(m->source, AL_GAIN, suppressAllGain); break; - case OverviewDocuments: alSourcef(m->source, AL_GAIN, suppressSongGain * cSongVolume()); break; + case OverviewVoiceFiles: alSourcef(m->source, AL_GAIN, suppressAllGain); break; + case OverviewFiles: alSourcef(m->source, AL_GAIN, suppressSongGain * cSongVolume()); break; } if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); @@ -1642,13 +1691,13 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const err = SetupErrorAtStart; QMutexLocker lock(&playerMutex); AudioPlayer *voice = audioPlayer(); - if (!voice) return 0; + if (!voice) return nullptr; bool isGoodId = false; AudioPlayer::Msg *m = 0; AudioPlayerLoader **l = 0; switch (type) { - case OverviewAudios: { + case OverviewVoiceFiles: { AudioPlayer::AudioMsg &msg(voice->_audioData[voice->_audioCurrent]); const AudioMsgId &audio(*static_cast(objId)); if (msg.audio != audio || !msg.loading) { @@ -1659,7 +1708,7 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const l = &_audioLoader; isGoodId = (_audio == audio); } break; - case OverviewDocuments: { + case OverviewFiles: { AudioPlayer::SongMsg &msg(voice->_songData[voice->_songCurrent]); const SongMsgId &song(*static_cast(objId)); if (msg.song != song || !msg.loading) { @@ -1674,22 +1723,22 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const if (!l || !m) { LOG(("Audio Error: trying to load part of audio, that is not current at the moment")); err = SetupErrorNotPlaying; - return 0; + return nullptr; } if (*l && (!isGoodId || !(*l)->check(m->file, m->data))) { delete *l; *l = 0; switch (type) { - case OverviewAudios: _audio = AudioMsgId(); break; - case OverviewDocuments: _song = SongMsgId(); break; + case OverviewVoiceFiles: _audio = AudioMsgId(); break; + case OverviewFiles: _song = SongMsgId(); break; } } if (!*l) { switch (type) { - case OverviewAudios: _audio = *static_cast(objId); break; - case OverviewDocuments: _song = *static_cast(objId); break; + case OverviewVoiceFiles: _audio = *static_cast(objId); break; + case OverviewFiles: _song = *static_cast(objId); break; } // QByteArray header = m->data.mid(0, 8); @@ -1698,27 +1747,26 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const // if (!f.open(QIODevice::ReadOnly)) { // LOG(("Audio Error: could not open file '%1'").arg(m->fname)); // m->state = AudioPlayerStoppedAtStart; -// return 0; +// return nullptr; // } // header = f.read(8); // } // if (header.size() < 8) { // LOG(("Audio Error: could not read header from file '%1', data size %2").arg(m->fname).arg(m->data.isEmpty() ? QFileInfo(m->fname).size() : m->data.size())); // m->state = AudioPlayerStoppedAtStart; -// return 0; +// return nullptr; // } *l = new FFMpegLoader(m->file, m->data); - int ret; if (!(*l)->open(position)) { m->state = AudioPlayerStoppedAtStart; - return 0; + return nullptr; } int64 duration = (*l)->duration(); if (duration <= 0) { m->state = AudioPlayerStoppedAtStart; - return 0; + return nullptr; } m->duration = duration; m->frequency = (*l)->frequency(); @@ -1728,7 +1776,7 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const if (!m->skipEnd) { err = SetupErrorLoadedFull; LOG(("Audio Error: trying to load part of audio, that is already loaded to the end")); - return 0; + return nullptr; } } return *l; @@ -1742,13 +1790,13 @@ AudioPlayer::Msg *AudioPlayerLoaders::checkLoader(MediaOverviewType type) { AudioPlayer::Msg *m = 0; AudioPlayerLoader **l = 0; switch (type) { - case OverviewAudios: { + case OverviewVoiceFiles: { AudioPlayer::AudioMsg &msg(voice->_audioData[voice->_audioCurrent]); isGoodId = (msg.audio == _audio); l = &_audioLoader; m = &msg; } break; - case OverviewDocuments: { + case OverviewFiles: { AudioPlayer::SongMsg &msg(voice->_songData[voice->_songCurrent]); isGoodId = (msg.song == _song); l = &_songLoader; @@ -1804,10 +1852,30 @@ void AudioPlayerLoaders::onCancel(const SongMsgId &song) { } struct AudioCapturePrivate { - AudioCapturePrivate() : - device(0), fmt(0), ioBuffer(0), ioContext(0), fmtContext(0), stream(0), codec(0), codecContext(0), opened(false), - srcSamples(0), dstSamples(0), maxDstSamples(0), dstSamplesSize(0), fullSamples(0), srcSamplesData(0), dstSamplesData(0), - swrContext(0), lastUpdate(0), level(0), dataPos(0) { + AudioCapturePrivate() + : device(0) + , fmt(0) + , ioBuffer(0) + , ioContext(0) + , fmtContext(0) + , stream(0) + , codec(0) + , codecContext(0) + , opened(false) + , srcSamples(0) + , dstSamples(0) + , maxDstSamples(0) + , dstSamplesSize(0) + , fullSamples(0) + , srcSamplesData(0) + , dstSamplesData(0) + , swrContext(0) + , lastUpdate(0) + , levelMax(0) + , dataPos(0) + , waveformMod(0) + , waveformEach(AudioVoiceMsgFrequency / 100) + , waveformPeak(0) { } ALCdevice *device; AVOutputFormat *fmt; @@ -1824,11 +1892,15 @@ struct AudioCapturePrivate { SwrContext *swrContext; int32 lastUpdate; - int64 level; + uint16 levelMax; QByteArray data; int32 dataPos; + int64 waveformMod, waveformEach; + uint16 waveformPeak; + QVector waveform; + static int _read_data(void *opaque, uint8_t *buf, int buf_size) { AudioCapturePrivate *l = reinterpret_cast(opaque); @@ -1884,7 +1956,7 @@ void AudioCaptureInner::onInit() { } void AudioCaptureInner::onStart() { - + // Start OpenAL Capture const ALCchar *dName = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); DEBUG_LOG(("Audio Info: Capture device name '%1'").arg(dName)); @@ -1905,7 +1977,7 @@ void AudioCaptureInner::onStart() { // Create encoding context d->ioBuffer = (uchar*)av_malloc(AVBlockSize); - + d->ioContext = avio_alloc_context(d->ioBuffer, AVBlockSize, 1, static_cast(d), &AudioCapturePrivate::_read_data, &AudioCapturePrivate::_write_data, &AudioCapturePrivate::_seek_data); int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; @@ -1962,7 +2034,7 @@ void AudioCaptureInner::onStart() { } // Open audio stream - if ((res = avcodec_open2(d->codecContext, d->codec, NULL)) < 0) { + if ((res = avcodec_open2(d->codecContext, d->codec, nullptr)) < 0) { LOG(("Audio Error: Unable to avcodec_open2 for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); onStop(false); emit error(); @@ -2040,6 +2112,9 @@ void AudioCaptureInner::onStop(bool needResult) { d->fullSamples = 0; d->dataPos = 0; d->data.clear(); + d->waveformMod = 0; + d->waveformPeak = 0; + d->waveform.clear(); } else { float64 coef = 1. / fadeSamples, fadedFrom = 0; for (short *ptr = ((short*)_captured.data()) + capturedSamples, *end = ptr - fadeSamples; ptr != end; ++fadedFrom) { @@ -2061,10 +2136,13 @@ void AudioCaptureInner::onStop(bool needResult) { d->fullSamples = 0; d->dataPos = 0; d->data.clear(); + d->waveformMod = 0; + d->waveformPeak = 0; + d->waveform.clear(); } } } - DEBUG_LOG(("Audio Capture: stopping (need result: %1), size: %2, samples: %3").arg(logBool(needResult)).arg(d->data.size()).arg(d->fullSamples)); + DEBUG_LOG(("Audio Capture: stopping (need result: %1), size: %2, samples: %3").arg(Logs::b(needResult)).arg(d->data.size()).arg(d->fullSamples)); _captured = QByteArray(); // Finish stream @@ -2073,7 +2151,37 @@ void AudioCaptureInner::onStop(bool needResult) { } QByteArray result = d->fullSamples ? d->data : QByteArray(); + VoiceWaveform waveform; qint32 samples = d->fullSamples; + if (samples && !d->waveform.isEmpty()) { + int64 count = d->waveform.size(), sum = 0; + if (count >= WaveformSamplesCount) { + QVector peaks; + peaks.reserve(WaveformSamplesCount); + + uint16 peak = 0; + for (int32 i = 0; i < count; ++i) { + uint16 sample = uint16(d->waveform.at(i)) * 256; + if (peak < sample) { + peak = sample; + } + sum += WaveformSamplesCount; + if (sum >= count) { + sum -= count; + peaks.push_back(peak); + peak = 0; + } + } + + int64 sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0ULL); + peak = qMax(int32(sum * 1.8 / peaks.size()), 2500); + + waveform.resize(peaks.size()); + for (int32 i = 0, l = peaks.size(); i != l; ++i) { + waveform[i] = char(qMin(31U, uint32(qMin(peaks.at(i), peak)) * 31 / peak)); + } + } + } if (d->device) { alcCaptureStop(d->device); alcCaptureCloseDevice(d->device); @@ -2121,12 +2229,16 @@ void AudioCaptureInner::onStop(bool needResult) { d->codec = 0; d->lastUpdate = 0; - d->level = 0; + d->levelMax = 0; d->dataPos = 0; d->data.clear(); + + d->waveformMod = 0; + d->waveformPeak = 0; + d->waveform.clear(); } - if (needResult) emit done(result, samples); + if (needResult) emit done(result, waveform, samples); } void AudioCaptureInner::onTimeout() { @@ -2160,18 +2272,20 @@ void AudioCaptureInner::onTimeout() { int32 levelindex = d->fullSamples + (s / sizeof(short)); for (const short *ptr = (const short*)(_captured.constData() + s), *end = (const short*)(_captured.constData() + news); ptr < end; ++ptr, ++levelindex) { if (levelindex > skipSamples) { + uint16 value = qAbs(*ptr); if (levelindex < skipSamples + fadeSamples) { - d->level += qRound(qAbs(*ptr) * float64(levelindex - skipSamples) / fadeSamples); - } else { - d->level += qAbs(*ptr); + value = qRound(value * float64(levelindex - skipSamples) / fadeSamples); + } + if (d->levelMax < value) { + d->levelMax = value; } } } qint32 samplesFull = d->fullSamples + _captured.size() / sizeof(short), samplesSinceUpdate = samplesFull - d->lastUpdate; if (samplesSinceUpdate > AudioVoiceMsgUpdateView * AudioVoiceMsgFrequency / 1000) { - emit update(d->level / samplesSinceUpdate, samplesFull); + emit update(d->levelMax, samplesFull); d->lastUpdate = samplesFull; - d->level = 0; + d->levelMax = 0; } // Write frames int32 framesize = d->srcSamples * d->codecContext->channels * sizeof(short), encoded = 0; @@ -2211,7 +2325,7 @@ void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) { if (d->fullSamples < skipSamples + fadeSamples) { int32 fadedCnt = qMin(samplesCnt, skipSamples + fadeSamples - d->fullSamples); float64 coef = 1. / fadeSamples, fadedFrom = d->fullSamples - skipSamples; - short *ptr = (short*)srcSamplesData[0], *zeroEnd = ptr + qMin(samplesCnt, qMax(0, skipSamples - d->fullSamples)), *end = ptr + fadedCnt; + short *ptr = srcSamplesDataChannel, *zeroEnd = ptr + qMin(samplesCnt, qMax(0, skipSamples - d->fullSamples)), *end = ptr + fadedCnt; for (; ptr != zeroEnd; ++ptr, ++fadedFrom) { *ptr = 0; } @@ -2220,6 +2334,19 @@ void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) { } } + d->waveform.reserve(d->waveform.size() + (samplesCnt / d->waveformEach) + 1); + for (short *ptr = srcSamplesDataChannel, *end = ptr + samplesCnt; ptr != end; ++ptr) { + uint16 value = qAbs(*ptr); + if (d->waveformPeak < value) { + d->waveformPeak = value; + } + if (++d->waveformMod == d->waveformEach) { + d->waveformMod -= d->waveformEach; + d->waveform.push_back(uchar(d->waveformPeak / 256)); + d->waveformPeak = 0; + } + } + // Convert to final format d->dstSamples = av_rescale_rnd(swr_get_delay(d->swrContext, d->codecContext->sample_rate) + d->srcSamples, d->codecContext->sample_rate, d->codecContext->sample_rate, AV_ROUND_UP); @@ -2274,65 +2401,25 @@ void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) { av_frame_free(&frame); } -class FFMpegAttributesReader : public AudioPlayerLoader { +class FFMpegAttributesReader : public AbstractFFMpegLoader { public: - FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data), - ioBuffer(0), ioContext(0), fmtContext(0), codec(0), streamId(0), - _opened(false) { + FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) { } bool open(qint64 position = 0) { - if (!AudioPlayerLoader::openFile()) { + if (!AbstractFFMpegLoader::open()) { return false; } - ioBuffer = (uchar*)av_malloc(AVBlockSize); - if (data.isEmpty()) { - ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegAttributesReader::_read_file, 0, &FFMpegAttributesReader::_seek_file); - } else { - ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegAttributesReader::_read_data, 0, &FFMpegAttributesReader::_seek_data); - } - fmtContext = avformat_alloc_context(); - if (!fmtContext) { - DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(fname).arg(data.size())); - return false; - } - fmtContext->pb = ioContext; - int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) { - ioBuffer = 0; - DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + int videoStreamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); + if (videoStreamId >= 0) { + DEBUG_LOG(("Audio Read Error: Found video stream in file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(videoStreamId).arg(av_make_error_string(err, sizeof(err), streamId))); return false; } - _opened = true; - - if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) { - DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - - streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); - if (streamId >= 0) { - DEBUG_LOG(("Audio Read Error: Found video stream in file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId))); - return false; - } - - streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); - if (streamId < 0) { - DEBUG_LOG(("Audio Read Error: Unable to av_find_best_stream for file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId))); - return false; - } - - freq = fmtContext->streams[streamId]->codec->sample_rate; - if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) { - len = (fmtContext->duration * freq) / AV_TIME_BASE; - } else { - len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den; - } for (int32 i = 0, l = fmtContext->nb_streams; i < l; ++i) { AVStream *stream = fmtContext->streams[i]; @@ -2375,14 +2462,6 @@ public: //} } - int64 duration() { - return len; - } - - int32 frequency() { - return freq; - } - int32 format() { return 0; } @@ -2390,7 +2469,7 @@ public: QString title() { return _title; } - + QString performer() { return _performer; } @@ -2413,77 +2492,14 @@ public: } ~FFMpegAttributesReader() { - if (ioContext) av_free(ioContext); - if (_opened) { - avformat_close_input(&fmtContext); - } else if (ioBuffer) { - av_free(ioBuffer); - } - if (fmtContext) avformat_free_context(fmtContext); } private: - QString fname, data; - - int32 freq; - int64 len; QString _title, _performer; QImage _cover; QByteArray _coverBytes, _coverFormat; - uchar *ioBuffer; - AVIOContext *ioContext; - AVFormatContext *fmtContext; - AVCodec *codec; - int32 streamId; - - bool _opened; - - static int _read_data(void *opaque, uint8_t *buf, int buf_size) { - FFMpegAttributesReader *l = reinterpret_cast(opaque); - - int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size)); - if (nbytes <= 0) { - return 0; - } - - memcpy(buf, l->data.constData() + l->dataPos, nbytes); - l->dataPos += nbytes; - return nbytes; - } - - static int64_t _seek_data(void *opaque, int64_t offset, int whence) { - FFMpegAttributesReader *l = reinterpret_cast(opaque); - - int32 newPos = -1; - switch (whence) { - case SEEK_SET: newPos = offset; break; - case SEEK_CUR: newPos = l->dataPos + offset; break; - case SEEK_END: newPos = l->data.size() + offset; break; - } - if (newPos < 0 || newPos > l->data.size()) { - return -1; - } - l->dataPos = newPos; - return l->dataPos; - } - - static int _read_file(void *opaque, uint8_t *buf, int buf_size) { - FFMpegAttributesReader *l = reinterpret_cast(opaque); - return int(l->f.read((char*)(buf), buf_size)); - } - - static int64_t _seek_file(void *opaque, int64_t offset, int whence) { - FFMpegAttributesReader *l = reinterpret_cast(opaque); - - switch (whence) { - case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1; - case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1; - case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1; - } - return -1; - } }; MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat) { @@ -2494,8 +2510,116 @@ MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteAr cover = reader.cover(); coverBytes = reader.coverBytes(); coverFormat = reader.coverFormat(); - return MTP_documentAttributeAudio(MTP_int(duration), MTP_string(reader.title()), MTP_string(reader.performer())); + return MTP_documentAttributeAudio(MTP_flags(MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer), MTP_int(duration), MTP_string(reader.title()), MTP_string(reader.performer()), MTPstring()); } } return MTP_documentAttributeFilename(MTP_string(fname)); } + +class FFMpegWaveformCounter : public FFMpegLoader { +public: + + FFMpegWaveformCounter(const FileLocation &file, const QByteArray &data) : FFMpegLoader(file, data) { + } + + bool open(qint64 position = 0) { + if (!FFMpegLoader::open(position)) { + return false; + } + + QByteArray buffer; + buffer.reserve(AudioVoiceMsgBufferSize); + int64 countbytes = sampleSize * duration(), processed = 0, sumbytes = 0; + if (duration() < WaveformSamplesCount) { + return false; + } + + QVector peaks; + peaks.reserve(WaveformSamplesCount); + + int32 fmt = format(); + uint16 peak = 0; + while (processed < countbytes) { + buffer.resize(0); + + int64 samples = 0; + int res = readMore(buffer, samples); + if (res < 0) { + break; + } + if (buffer.isEmpty()) { + continue; + } + + const char *data = buffer.data(); + if (fmt == AL_FORMAT_MONO8 || fmt == AL_FORMAT_STEREO8) { + for (int32 i = 0, l = buffer.size(); i + int32(sizeof(uchar)) <= l;) { + uint16 sample = qAbs((int32(*(uchar*)(data + i)) - 128) * 256); + if (peak < sample) { + peak = sample; + } + + i += sizeof(uchar); + sumbytes += WaveformSamplesCount; + if (sumbytes >= countbytes) { + sumbytes -= countbytes; + peaks.push_back(peak); + peak = 0; + } + } + } else if (fmt == AL_FORMAT_MONO16 || fmt == AL_FORMAT_STEREO16) { + for (int32 i = 0, l = buffer.size(); i + int32(sizeof(uint16)) <= l;) { + uint16 sample = qAbs(int32(*(int16*)(data + i))); + if (peak < sample) { + peak = sample; + } + + i += sizeof(uint16); + sumbytes += sizeof(uint16) * WaveformSamplesCount; + if (sumbytes >= countbytes) { + sumbytes -= countbytes; + peaks.push_back(peak); + peak = 0; + } + } + } + processed += sampleSize * samples; + } + if (sumbytes > 0 && peaks.size() < WaveformSamplesCount) { + peaks.push_back(peak); + } + + if (peaks.isEmpty()) { + return false; + } + + int64 sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0ULL); + peak = qMax(int32(sum * 1.8 / peaks.size()), 2500); + + result.resize(peaks.size()); + for (int32 i = 0, l = peaks.size(); i != l; ++i) { + result[i] = char(qMin(31U, uint32(qMin(peaks.at(i), peak)) * 31 / peak)); + } + + return true; + } + + const VoiceWaveform &waveform() const { + return result; + } + + ~FFMpegWaveformCounter() { + } + +private: + VoiceWaveform result; + +}; + +VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data) { + FFMpegWaveformCounter counter(file, data); + if (counter.open()) { + return counter.waveform(); + } + return VoiceWaveform(); +} diff --git a/Telegram/SourceFiles/audio.h b/Telegram/SourceFiles/audio.h index 9413233501..4eb75c88dd 100644 --- a/Telegram/SourceFiles/audio.h +++ b/Telegram/SourceFiles/audio.h @@ -16,11 +16,11 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "types.h" +#include "basic_types.h" void audioInit(); bool audioWorks(); @@ -56,7 +56,7 @@ public: void play(const AudioMsgId &audio, int64 position = 0); void play(const SongMsgId &song, int64 position = 0); void pauseresume(MediaOverviewType type, bool fast = false); - void seek(int64 position); // type == OverviewDocuments + void seek(int64 position); // type == OverviewFiles void stop(MediaOverviewType type); void stopAndClear(); @@ -201,8 +201,8 @@ signals: void captureOnStart(); void captureOnStop(bool needResult); - void onDone(QByteArray data, qint32 samples); - void onUpdate(qint16 level, qint32 samples); + void onDone(QByteArray data, VoiceWaveform waveform, qint32 samples); + void onUpdate(quint16 level, qint32 samples); void onError(); private: @@ -338,8 +338,8 @@ public: signals: void error(); - void update(qint16 level, qint32 samples); - void done(QByteArray data, qint32 samples); + void update(quint16 level, qint32 samples); + void done(QByteArray data, VoiceWaveform waveform, qint32 samples); public slots: @@ -360,3 +360,4 @@ private: }; MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat); +VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data); diff --git a/Telegram/SourceFiles/autoupdater.cpp b/Telegram/SourceFiles/autoupdater.cpp index a1a77a4ff0..6c46f60c18 100644 --- a/Telegram/SourceFiles/autoupdater.cpp +++ b/Telegram/SourceFiles/autoupdater.cpp @@ -16,13 +16,26 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" + +#include "autoupdater.h" + +#include +#include +#include +#include + +#ifdef Q_OS_WIN // use Lzma SDK for win +#include +#else // Q_OS_WIN +#include +#endif // else of Q_OS_WIN + #include "application.h" #include "pspecific.h" -#include "autoupdater.h" #ifndef TDESKTOP_DISABLE_AUTOUPDATE @@ -34,7 +47,7 @@ typedef int VerInt; typedef wchar_t VerChar; #endif -UpdateDownloader::UpdateDownloader(QThread *thread, const QString &url) : reply(0), already(0), full(0) { +UpdateChecker::UpdateChecker(QThread *thread, const QString &url) : reply(0), already(0), full(0) { updateUrl = url; moveToThread(thread); manager.moveToThread(thread); @@ -44,14 +57,14 @@ UpdateDownloader::UpdateDownloader(QThread *thread, const QString &url) : reply( initOutput(); } -void UpdateDownloader::initOutput() { +void UpdateChecker::initOutput() { QString fileName; QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl); if (m.hasMatch()) { fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString()); } if (fileName.isEmpty()) { - fileName = qsl("tupdate-%1").arg(MTP::nonce() % 1000000); + fileName = qsl("tupdate-%1").arg(rand_value() % 1000000); } QString dirStr = cWorkingDir() + qsl("tupdates/"); fileName = dirStr + fileName; @@ -99,11 +112,11 @@ void UpdateDownloader::initOutput() { } } -void UpdateDownloader::start() { +void UpdateChecker::start() { sendRequest(); } -void UpdateDownloader::sendRequest() { +void UpdateChecker::sendRequest() { QNetworkRequest req(updateUrl); QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-"; req.setRawHeader("Range", rangeHeaderValue); @@ -115,7 +128,7 @@ void UpdateDownloader::sendRequest() { connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot())); } -void UpdateDownloader::partMetaGot() { +void UpdateChecker::partMetaGot() { typedef QList Pairs; Pairs pairs = reply->rawHeaderPairs(); for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) { @@ -126,23 +139,24 @@ void UpdateDownloader::partMetaGot() { QMutexLocker lock(&mutex); full = m.captured(1).toInt(); } - emit App::app()->updateDownloading(already, full); + + Sandbox::updateProgress(already, full); } } } } -int32 UpdateDownloader::ready() { +int32 UpdateChecker::ready() { QMutexLocker lock(&mutex); return already; } -int32 UpdateDownloader::size() { +int32 UpdateChecker::size() { QMutexLocker lock(&mutex); return full; } -void UpdateDownloader::partFinished(qint64 got, qint64 total) { +void UpdateChecker::partFinished(qint64 got, qint64 total) { if (!reply) return; QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); @@ -179,11 +193,11 @@ void UpdateDownloader::partFinished(qint64 got, qint64 total) { outputFile.close(); unpackUpdate(); } else { - emit App::app()->updateDownloading(already, full); + Sandbox::updateProgress(already, full); } } -void UpdateDownloader::partFailed(QNetworkReply::NetworkError e) { +void UpdateChecker::partFailed(QNetworkReply::NetworkError e) { if (!reply) return; QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); @@ -198,15 +212,15 @@ void UpdateDownloader::partFailed(QNetworkReply::NetworkError e) { } } LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e)); - emit App::app()->updateFailed(); + Sandbox::updateFailed(); } -void UpdateDownloader::fatalFail() { +void UpdateChecker::fatalFail() { clearAll(); - emit App::app()->updateFailed(); + Sandbox::updateFailed(); } -void UpdateDownloader::clearAll() { +void UpdateChecker::clearAll() { psDeleteDir(cWorkingDir() + qsl("tupdates")); } @@ -225,7 +239,7 @@ void UpdateDownloader::clearAll() { // return QString::fromWCharArray(errMsg); //} -void UpdateDownloader::unpackUpdate() { +void UpdateChecker::unpackUpdate() { QByteArray packed; if (!outputFile.open(QIODevice::ReadOnly)) { LOG(("Update Error: cant read updates file!")); @@ -465,10 +479,10 @@ void UpdateDownloader::unpackUpdate() { } outputFile.remove(); - emit App::app()->updateReady(); + Sandbox::updateReady(); } -UpdateDownloader::~UpdateDownloader() { +UpdateChecker::~UpdateChecker() { delete reply; reply = 0; } @@ -477,7 +491,7 @@ bool checkReadyUpdate() { QString readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"), readyPath = cWorkingDir() + qsl("tupdates/temp"); if (!QFile(readyFilePath).exists()) { if (QDir(cWorkingDir() + qsl("tupdates/ready")).exists() || QDir(cWorkingDir() + qsl("tupdates/temp")).exists()) { - UpdateDownloader::clearAll(); + UpdateChecker::clearAll(); } return false; } @@ -488,30 +502,30 @@ bool checkReadyUpdate() { QFile fVersion(versionPath); if (!fVersion.open(QIODevice::ReadOnly)) { LOG(("Update Error: cant read version file '%1'").arg(versionPath)); - UpdateDownloader::clearAll(); + UpdateChecker::clearAll(); return false; } VerInt versionNum; if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) { LOG(("Update Error: cant read version from file '%1'").arg(versionPath)); - UpdateDownloader::clearAll(); + UpdateChecker::clearAll(); return false; } if (versionNum == 0x7FFFFFFF) { // beta version quint64 betaVersion = 0; if (fVersion.read((char*)&betaVersion, sizeof(quint64)) != sizeof(quint64)) { LOG(("Update Error: cant read beta version from file '%1'").arg(versionPath)); - UpdateDownloader::clearAll(); + UpdateChecker::clearAll(); return false; } if (!cBetaVersion() || betaVersion <= cBetaVersion()) { LOG(("Update Error: cant install beta version %1 having beta version %2").arg(betaVersion).arg(cBetaVersion())); - UpdateDownloader::clearAll(); + UpdateChecker::clearAll(); return false; } } else if (versionNum <= AppVersion) { LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion)); - UpdateDownloader::clearAll(); + UpdateChecker::clearAll(); return false; } fVersion.close(); @@ -530,11 +544,11 @@ bool checkReadyUpdate() { if (!updater.exists()) { QFileInfo current(curUpdater); if (!current.exists()) { - UpdateDownloader::clearAll(); + UpdateChecker::clearAll(); return false; } if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) { - UpdateDownloader::clearAll(); + UpdateChecker::clearAll(); return false; } } @@ -545,24 +559,24 @@ bool checkReadyUpdate() { cSetWriteProtected(true); return true; } else { - UpdateDownloader::clearAll(); + UpdateChecker::clearAll(); return false; } } if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) { - UpdateDownloader::clearAll(); + UpdateChecker::clearAll(); return false; } #elif defined Q_OS_MAC QDir().mkpath(QFileInfo(curUpdater).absolutePath()); - DEBUG_LOG(("Update Info: moving %1 to %2..").arg(updater.absoluteFilePath()).arg(curUpdater)); + DEBUG_LOG(("Update Info: moving %1 to %2...").arg(updater.absoluteFilePath()).arg(curUpdater)); if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) { - UpdateDownloader::clearAll(); + UpdateChecker::clearAll(); return false; } #elif defined Q_OS_LINUX if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) { - UpdateDownloader::clearAll(); + UpdateChecker::clearAll(); return false; } #endif @@ -578,7 +592,7 @@ QString countBetaVersionSignature(uint64 version) { // duplicated in packer.cpp } QByteArray signedData = (qstr("TelegramBeta_") + QString::number(version, 16).toLower()).toUtf8(); - + static const int32 shaSize = 20, keySize = 128; uchar sha1Buffer[shaSize]; diff --git a/Telegram/SourceFiles/autoupdater.h b/Telegram/SourceFiles/autoupdater.h index e2f2a9ac9b..3687a405dc 100644 --- a/Telegram/SourceFiles/autoupdater.h +++ b/Telegram/SourceFiles/autoupdater.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -26,11 +26,11 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include #include -class UpdateDownloader : public QObject { +class UpdateChecker : public QObject { Q_OBJECT public: - UpdateDownloader(QThread *thread, const QString &url); + UpdateChecker(QThread *thread, const QString &url); void unpackUpdate(); @@ -39,7 +39,7 @@ public: static void clearAll(); - ~UpdateDownloader(); + ~UpdateChecker(); public slots: @@ -66,6 +66,11 @@ private: bool checkReadyUpdate(); +#else +class UpdateChecker : public QObject { + Q_OBJECT +}; + #endif QString countBetaVersionSignature(uint64 version); diff --git a/Telegram/SourceFiles/types.cpp b/Telegram/SourceFiles/basic_types.cpp similarity index 92% rename from Telegram/SourceFiles/types.cpp rename to Telegram/SourceFiles/basic_types.cpp index 25a198f234..5d1bc68749 100644 --- a/Telegram/SourceFiles/types.cpp +++ b/Telegram/SourceFiles/basic_types.cpp @@ -16,10 +16,20 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" +#include "basic_types.h" + +#include +#include + +extern "C" { +#include +#include +} + #include "application.h" uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 }; @@ -35,6 +45,10 @@ uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 }; // Base types compile-time check +#ifdef TDESKTOP_CUSTOM_NULLPTR +NullPointerClass nullptr; +#endif + namespace { template class _TypeSizeCheckerHelper { @@ -99,15 +113,15 @@ namespace { clock_gettime(CLOCK_REALTIME, &ts); _msgIdMsStart = 1000000000 * uint64(ts.tv_sec) + uint64(ts.tv_nsec); #endif - + uint32 msgIdRand; memset_rand(&msgIdRand, sizeof(uint32)); _msgIdStart = (((uint64)((uint32)unixtime()) << 32) | (uint64)msgIdRand); } } -int32 myunixtime() { - return (int32)time(NULL); +TimeId myunixtime() { + return (TimeId)time(NULL); } void unixtimeInit() { @@ -135,19 +149,19 @@ void unixtimeSet(int32 serverTime, bool force) { _initMsgIdConstants(); } -int32 unixtime() { - int32 result = myunixtime(); +TimeId unixtime() { + TimeId result = myunixtime(); QReadLocker locker(&unixtimeLock); return result + unixtimeDelta; } -int32 fromServerTime(const MTPint &serverTime) { +TimeId fromServerTime(const MTPint &serverTime) { QReadLocker locker(&unixtimeLock); return serverTime.v - unixtimeDelta; } -MTPint toServerTime(const int32 &clientTime) { +MTPint toServerTime(const TimeId &clientTime) { QReadLocker locker(&unixtimeLock); return MTP_int(clientTime + unixtimeDelta); } @@ -187,6 +201,32 @@ namespace { delete l; } + int _ffmpegLockManager(void **mutex, AVLockOp op) { + switch (op) { + case AV_LOCK_CREATE: { + t_assert(*mutex == 0); + *mutex = reinterpret_cast(new QMutex()); + } break; + + case AV_LOCK_OBTAIN: { + t_assert(*mutex != 0); + reinterpret_cast(*mutex)->lock(); + } break; + + case AV_LOCK_RELEASE: { + t_assert(*mutex != 0); + reinterpret_cast(*mutex)->unlock(); + }; break; + + case AV_LOCK_DESTROY: { + t_assert(*mutex != 0); + delete reinterpret_cast(*mutex); + *mutex = 0; + } break; + } + return 0; + } + float64 _msFreq; float64 _msgIdCoef; int64 _msStart = 0, _msAddToMsStart = 0, _msAddToUnixtime = 0; @@ -238,36 +278,51 @@ namespace { _MsStarter _msStarter; } -InitOpenSSL::InitOpenSSL() { - if (!RAND_status()) { // should be always inited in all modern OS - char buf[16]; - memcpy(buf, &_msStart, 8); - memcpy(buf + 8, &_msFreq, 8); - uchar sha256Buffer[32]; - RAND_seed(hashSha256(buf, 16, sha256Buffer), 32); - if (!RAND_status()) { - LOG(("MTP Error: Could not init OpenSSL rand, RAND_status() is 0..")); +namespace ThirdParty { + + void start() { + PlatformSpecific::ThirdParty::start(); + + if (!RAND_status()) { // should be always inited in all modern OS + char buf[16]; + memcpy(buf, &_msStart, 8); + memcpy(buf + 8, &_msFreq, 8); + uchar sha256Buffer[32]; + RAND_seed(hashSha256(buf, 16, sha256Buffer), 32); + if (!RAND_status()) { + LOG(("MTP Error: Could not init OpenSSL rand, RAND_status() is 0...")); + } } + + int32 numLocks = CRYPTO_num_locks(); + if (numLocks) { + _sslLocks = new QMutex[numLocks]; + CRYPTO_set_locking_callback(_sslLockingCallback); + } else { + LOG(("MTP Error: Could not init OpenSSL threads, CRYPTO_num_locks() returned zero!")); + } + CRYPTO_THREADID_set_callback(_sslThreadId); + CRYPTO_set_dynlock_create_callback(_sslCreateFunction); + CRYPTO_set_dynlock_lock_callback(_sslLockFunction); + CRYPTO_set_dynlock_destroy_callback(_sslDestroyFunction); + + av_register_all(); + avcodec_register_all(); + + av_lockmgr_register(_ffmpegLockManager); + + _sslInited = true; } - int32 numLocks = CRYPTO_num_locks(); - if (numLocks) { - _sslLocks = new QMutex[numLocks]; - CRYPTO_set_locking_callback(_sslLockingCallback); - } else { - LOG(("MTP Error: Could not init OpenSSL threads, CRYPTO_num_locks() returned zero!")); + void finish() { + av_lockmgr_register(0); + + delete[] _sslLocks; + _sslLocks = 0; + + PlatformSpecific::ThirdParty::finish(); } - CRYPTO_THREADID_set_callback(_sslThreadId); - CRYPTO_set_dynlock_create_callback(_sslCreateFunction); - CRYPTO_set_dynlock_lock_callback(_sslLockFunction); - CRYPTO_set_dynlock_destroy_callback(_sslDestroyFunction); - _sslInited = true; -} - -InitOpenSSL::~InitOpenSSL() { - delete[] _sslLocks; - _sslLocks = 0; } bool checkms() { @@ -640,10 +695,7 @@ char *hashMd5Hex(const int32 *hashmd5, void *dest) { } void memset_rand(void *data, uint32 len) { - if (!_sslInited) { - LOG(("Critical Error: memset_rand() called before OpenSSL init!")); - exit(-1); - } + t_assert(_sslInited); RAND_bytes((uchar*)data, len); } @@ -777,8 +829,7 @@ QString translitRusEng(const QString &rus) { result.reserve(rus.size() * 2); int32 toSkip = 0; - for (QString::const_iterator i = rus.cbegin(), e = rus.cend(); i != e;) { - i += toSkip; + for (QString::const_iterator i = rus.cbegin(), e = rus.cend(); i != e; i += toSkip) { result += translitLetterRusEng(*i, (i + 1 == e) ? ' ' : *(i + 1), toSkip); } return result; @@ -982,3 +1033,33 @@ MimeType mimeTypeForData(const QByteArray &data) { } return MimeType(QMimeDatabase().mimeTypeForData(data)); } + +struct ComposerMetadatasMap { + QMap data; + ~ComposerMetadatasMap() { + for_const (const ComposerMetadata *p, data) { + delete p; + } + } +}; + +const ComposerMetadata *GetComposerMetadata(uint64 mask) { + static ComposerMetadatasMap ComposerMetadatas; + static QMutex ComposerMetadatasMutex; + + QMutexLocker lock(&ComposerMetadatasMutex); + auto i = ComposerMetadatas.data.constFind(mask); + if (i == ComposerMetadatas.data.cend()) { + ComposerMetadata *meta = new ComposerMetadata(mask); + t_assert(meta != nullptr); + + i = ComposerMetadatas.data.insert(mask, meta); + } + return i.value(); +} + +const ComposerMetadata *Composer::ZeroComposerMetadata = GetComposerMetadata(0); + +ComponentWrapStruct ComponentWraps[64]; + +QAtomicInt ComponentIndexLast; diff --git a/Telegram/SourceFiles/basic_types.h b/Telegram/SourceFiles/basic_types.h new file mode 100644 index 0000000000..62c9c40564 --- /dev/null +++ b/Telegram/SourceFiles/basic_types.h @@ -0,0 +1,1362 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +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 +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +template +void deleteAndMark(T *&link) { + delete link; + link = reinterpret_cast(0x00000BAD); +} + +template +T *getPointerAndReset(T *&ptr) { + T *result = nullptr; + qSwap(result, ptr); + return result; +} + +struct NullType { +}; + +// ordered set template based on QMap +template +class OrderedSet { + typedef OrderedSet Self; + typedef QMap Impl; + typedef typename Impl::iterator IteratorImpl; + typedef typename Impl::const_iterator ConstIteratorImpl; + Impl impl_; + +public: + + inline bool operator==(const Self &other) const { return impl_ == other.impl_; } + inline bool operator!=(const Self &other) const { return impl_ != other.impl_; } + inline int size() const { return impl_.size(); } + inline bool isEmpty() const { return impl_.isEmpty(); } + inline void detach() { return impl_.detach(); } + inline bool isDetached() const { return impl_.isDetached(); } + inline void clear() { return impl_.clear(); } + inline QList values() const { return impl_.keys(); } + inline const T &first() const { return impl_.firstKey(); } + inline const T &last() const { return impl_.lastKey(); } + + class const_iterator; + class iterator { + public: + typedef typename IteratorImpl::iterator_category iterator_category; + typedef typename IteratorImpl::difference_type difference_type; + typedef T value_type; + typedef T *pointer; + typedef T &reference; + + explicit iterator(const IteratorImpl &impl) : impl_(impl) { + } + inline const T &operator*() const { return impl_.key(); } + inline const T *operator->() const { return &impl_.key(); } + inline bool operator==(const iterator &other) const { return impl_ == other.impl_; } + inline bool operator!=(const iterator &other) const { return impl_ != other.impl_; } + inline iterator &operator++() { ++impl_; return *this; } + inline iterator operator++(int) { return iterator(impl_++); } + inline iterator &operator--() { --impl_; return *this; } + inline iterator operator--(int) { return iterator(impl_--); } + inline iterator operator+(int j) const { return iterator(impl_ + j); } + inline iterator operator-(int j) const { return iterator(impl_ - j); } + inline iterator &operator+=(int j) { impl_ += j; return *this; } + inline iterator &operator-=(int j) { impl_ -= j; return *this; } + + friend class const_iterator; + inline bool operator==(const const_iterator &other) const { return impl_ == other.impl_; } + inline bool operator!=(const const_iterator &other) const { return impl_ != other.impl_; } + + private: + IteratorImpl impl_; + friend class OrderedSet; + + }; + friend class iterator; + + class const_iterator { + public: + typedef typename IteratorImpl::iterator_category iterator_category; + typedef typename IteratorImpl::difference_type difference_type; + typedef T value_type; + typedef T *pointer; + typedef T &reference; + + explicit const_iterator(const ConstIteratorImpl &impl) : impl_(impl) { + } + inline const T &operator*() const { return impl_.key(); } + inline const T *operator->() const { return &impl_.key(); } + inline bool operator==(const const_iterator &other) const { return impl_ == other.impl_; } + inline bool operator!=(const const_iterator &other) const { return impl_ != other.impl_; } + inline const_iterator &operator++() { ++impl_; return *this; } + inline const_iterator operator++(int) { return const_iterator(impl_++); } + inline const_iterator &operator--() { --impl_; return *this; } + inline const_iterator operator--(int) { return const_iterator(impl_--); } + inline const_iterator operator+(int j) const { return const_iterator(impl_ + j); } + inline const_iterator operator-(int j) const { return const_iterator(impl_ - j); } + inline const_iterator &operator+=(int j) { impl_ += j; return *this; } + inline const_iterator &operator-=(int j) { impl_ -= j; return *this; } + + friend class iterator; + inline bool operator==(const iterator &other) const { return impl_ == other.impl_; } + inline bool operator!=(const iterator &other) const { return impl_ != other.impl_; } + + private: + ConstIteratorImpl impl_; + friend class OrderedSet; + + }; + friend class const_iterator; + + // STL style + inline iterator begin() { return iterator(impl_.begin()); } + inline const_iterator begin() const { return const_iterator(impl_.cbegin()); } + inline const_iterator constBegin() const { return const_iterator(impl_.cbegin()); } + inline const_iterator cbegin() const { return const_iterator(impl_.cbegin()); } + inline iterator end() { detach(); return iterator(impl_.end()); } + inline const_iterator end() const { return const_iterator(impl_.cend()); } + inline const_iterator constEnd() const { return const_iterator(impl_.cend()); } + inline const_iterator cend() const { return const_iterator(impl_.cend()); } + inline iterator erase(iterator it) { return iterator(impl_.erase(it.impl_)); } + + inline iterator insert(const T &value) { return iterator(impl_.insert(value, NullType())); } + inline iterator insert(const_iterator pos, const T &value) { return iterator(impl_.insert(pos.impl_, value, NullType())); } + inline int remove(const T &value) { return impl_.remove(value); } + inline bool contains(const T &value) const { return impl_.contains(value); } + + // more Qt + typedef iterator Iterator; + typedef const_iterator ConstIterator; + inline int count() const { return impl_.count(); } + inline iterator find(const T &value) { return iterator(impl_.find(value)); } + inline const_iterator find(const T &value) const { return const_iterator(impl_.constFind(value)); } + inline const_iterator constFind(const T &value) const { return const_iterator(impl_.constFind(value)); } + inline Self &unite(const Self &other) { impl_.unite(other.impl_); return *this; } + + // STL compatibility + typedef typename Impl::difference_type difference_type; + typedef typename Impl::size_type size_type; + inline bool empty() const { return impl_.empty(); } + +}; + +// thanks Chromium see https://blogs.msdn.microsoft.com/the1/2004/05/07/how-would-you-get-the-count-of-an-array-in-c-2/ +template char(&ArraySizeHelper(T(&array)[N]))[N]; +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + +#define qsl(s) QStringLiteral(s) +#define qstr(s) QLatin1String(s, sizeof(s) - 1) + +// using for_const instead of plain range-based for loop to ensure usage of const_iterator +// it is important for the copy-on-write Qt containers +// if you have "QVector v" then "for (T * const p : v)" will still call QVector::detach(), +// while "for_const (T *p, v)" won't and "for_const (T *&p, v)" won't compile +#define for_const(range_declaration, range_expression) for (range_declaration : std_::as_const(range_expression)) + +template +inline QFlags qFlags(Enum v) { + return QFlags(v); +} + +//typedef unsigned char uchar; // Qt has uchar +typedef qint16 int16; +typedef quint16 uint16; +typedef qint32 int32; +typedef quint32 uint32; +typedef qint64 int64; +typedef quint64 uint64; + +static const int32 ScrollMax = INT_MAX; + +extern uint64 _SharedMemoryLocation[]; +template +T *SharedMemoryLocation() { + static_assert(N < 4, "Only 4 shared memory locations!"); + return reinterpret_cast(_SharedMemoryLocation + N); +} + +// see https://github.com/boostcon/cppnow_presentations_2012/blob/master/wed/schurr_cpp11_tools_for_class_authors.pdf +class str_const { // constexpr string +public: + template + constexpr str_const(const char(&a)[N]) : _str(a), _size(N - 1) { + } + constexpr char operator[](std::size_t n) const { + return (n < _size) ? _str[n] : + throw std::out_of_range(""); + } + constexpr std::size_t size() const { return _size; } + const char *c_str() const { return _str; } + +private: + const char* const _str; + const std::size_t _size; + +}; + +template +inline void accumulate_max(T &a, const T &b) { if (a < b) a = b; } + +template +inline void accumulate_min(T &a, const T &b) { if (a > b) a = b; } + +#ifdef Q_OS_WIN +typedef float float32; +typedef double float64; +#else +typedef float float32; +typedef double float64; +#endif + +#include +#include + +#include + +#include + +using std::string; +using std::exception; + +// we copy some parts of C++11/14/17 std:: library, because on OS X 10.6+ +// version we can use C++11/14/17, but we can not use its library :( +namespace std_ { + +template +struct integral_constant { + static constexpr T value = V; + + using value_type = T; + using type = integral_constant; + + constexpr operator value_type() const noexcept { + return (value); + } + + constexpr value_type operator()() const noexcept { + return (value); + } +}; + +using true_type = integral_constant; +using false_type = integral_constant; + +template +struct remove_reference { + using type = T; +}; +template +struct remove_reference { + using type = T; +}; +template +struct remove_reference { + using type = T; +}; + +template +struct is_lvalue_reference : false_type { +}; +template +struct is_lvalue_reference : true_type { +}; + +template +struct is_rvalue_reference : false_type { +}; +template +struct is_rvalue_reference : true_type { +}; + +template +inline constexpr T &&forward(typename remove_reference::type &value) noexcept { + return static_cast(value); +} +template +inline constexpr T &&forward(typename remove_reference::type &&value) noexcept { + static_assert(!is_lvalue_reference::value, "bad forward call"); + return static_cast(value); +} + +template +inline constexpr typename remove_reference::type &&move(T &&value) noexcept { + return static_cast::type&&>(value); +} + +template +struct add_const { + using type = const T; +}; +template +using add_const_t = typename add_const::type; +template +constexpr add_const_t &as_const(T& t) noexcept { + return t; +} +template +void as_const(const T&&) = delete; + +} // namespace std_ + +#include "logs.h" + +static volatile int *t_assert_nullptr = nullptr; +inline void t_noop() {} +inline void t_assert_fail(const char *message, const char *file, int32 line) { + QString info(qsl("%1 %2:%3").arg(message).arg(file).arg(line)); + LOG(("Assertion Failed! %1 %2:%3").arg(info)); + SignalHandlers::setCrashAnnotation("Assertion", info); + *t_assert_nullptr = 0; +} +#define t_assert_full(condition, message, file, line) ((!(condition)) ? t_assert_fail(message, file, line) : t_noop()) +#define t_assert_c(condition, comment) t_assert_full(condition, "\"" #condition "\" (" comment ")", __FILE__, __LINE__) +#define t_assert(condition) t_assert_full(condition, "\"" #condition "\"", __FILE__, __LINE__) + +class Exception : public exception { +public: + + Exception(const QString &msg, bool isFatal = true) : _fatal(isFatal), _msg(msg.toUtf8()) { + LOG(("Exception: %1").arg(msg)); + } + bool fatal() const { + return _fatal; + } + + virtual const char *what() const throw() { + return _msg.constData(); + } + virtual ~Exception() throw() { + } + +private: + bool _fatal; + QByteArray _msg; +}; + +class MTPint; +typedef int32 TimeId; +TimeId myunixtime(); +void unixtimeInit(); +void unixtimeSet(TimeId servertime, bool force = false); +TimeId unixtime(); +TimeId fromServerTime(const MTPint &serverTime); +uint64 msgid(); +int32 reqid(); + +inline QDateTime date(int32 time = -1) { + QDateTime result; + if (time >= 0) result.setTime_t(time); + return result; +} + +inline QDateTime date(const MTPint &time) { + return date(fromServerTime(time)); +} + +inline void mylocaltime(struct tm * _Tm, const time_t * _Time) { +#ifdef Q_OS_WIN + localtime_s(_Tm, _Time); +#else + localtime_r(_Time, _Tm); +#endif +} + +namespace ThirdParty { + + void start(); + void finish(); + +} + +bool checkms(); // returns true if time has changed +uint64 getms(bool checked = false); + +class SingleTimer : public QTimer { // single shot timer with check + Q_OBJECT + +public: + + SingleTimer(); + + void setSingleShot(bool); // is not available + void start(); // is not available + +public slots: + + void start(int msec); + void startIfNotActive(int msec); + void adjust() { + uint64 n = getms(true); + if (isActive()) { + if (n >= _finishing) { + start(0); + } else { + start(_finishing - n); + } + } + } + +private: + uint64 _finishing; + bool _inited; + +}; + +const static uint32 _md5_block_size = 64; +class HashMd5 { +public: + + HashMd5(const void *input = 0, uint32 length = 0); + void feed(const void *input, uint32 length); + int32 *result(); + +private: + + void init(); + void finalize(); + void transform(const uchar *block); + + bool _finalized; + uchar _buffer[_md5_block_size]; + uint32 _count[2]; + uint32 _state[4]; + uchar _digest[16]; + +}; + +int32 hashCrc32(const void *data, uint32 len); +int32 *hashSha1(const void *data, uint32 len, void *dest); // dest - ptr to 20 bytes, returns (int32*)dest +int32 *hashSha256(const void *data, uint32 len, void *dest); // dest - ptr to 32 bytes, returns (int32*)dest +int32 *hashMd5(const void *data, uint32 len, void *dest); // dest = ptr to 16 bytes, returns (int32*)dest +char *hashMd5Hex(const int32 *hashmd5, void *dest); // dest = ptr to 32 bytes, returns (char*)dest +inline char *hashMd5Hex(const void *data, uint32 len, void *dest) { // dest = ptr to 32 bytes, returns (char*)dest + return hashMd5Hex(HashMd5(data, len).result(), dest); +} + +// good random (using openssl implementation) +void memset_rand(void *data, uint32 len); +template +T rand_value() { + T result; + memset_rand(&result, sizeof(result)); + return result; +} + +inline void memset_rand_bad(void *data, uint32 len) { + for (uchar *i = reinterpret_cast(data), *e = i + len; i != e; ++i) { + *i = uchar(rand() & 0xFF); + } +} + +template +inline void memsetrnd_bad(T &value) { + memset_rand_bad(&value, sizeof(value)); +} + +class ReadLockerAttempt { +public: + + ReadLockerAttempt(QReadWriteLock *_lock) : success(_lock->tryLockForRead()), lock(_lock) { + } + ~ReadLockerAttempt() { + if (success) { + lock->unlock(); + } + } + + operator bool() const { + return success; + } + +private: + + bool success; + QReadWriteLock *lock; + +}; + +inline QString fromUtf8Safe(const char *str, int32 size = -1) { + if (!str || !size) return QString(); + if (size < 0) size = int32(strlen(str)); + QString result(QString::fromUtf8(str, size)); + QByteArray back = result.toUtf8(); + if (back.size() != size || memcmp(back.constData(), str, size)) return QString::fromLocal8Bit(str, size); + return result; +} + +inline QString fromUtf8Safe(const QByteArray &str) { + return fromUtf8Safe(str.constData(), str.size()); +} + +static const QRegularExpression::PatternOptions reMultiline(QRegularExpression::DotMatchesEverythingOption | QRegularExpression::MultilineOption); + +template +inline T snap(const T &v, const T &_min, const T &_max) { + return (v < _min) ? _min : ((v > _max) ? _max : v); +} + +template +class ManagedPtr { +public: + ManagedPtr() : ptr(0) { + } + ManagedPtr(T *p) : ptr(p) { + } + T *operator->() const { + return ptr; + } + T *v() const { + return ptr; + } + +protected: + + T *ptr; + typedef ManagedPtr Parent; +}; + +QString translitRusEng(const QString &rus); +QString rusKeyboardLayoutSwitch(const QString &from); + +enum DBISendKey { + dbiskEnter = 0, + dbiskCtrlEnter = 1, +}; + +enum DBINotifyView { + dbinvShowPreview = 0, + dbinvShowName = 1, + dbinvShowNothing = 2, +}; + +enum DBIWorkMode { + dbiwmWindowAndTray = 0, + dbiwmTrayOnly = 1, + dbiwmWindowOnly = 2, +}; + +enum DBIConnectionType { + dbictAuto = 0, + dbictHttpAuto = 1, // not used + dbictHttpProxy = 2, + dbictTcpProxy = 3, +}; + +enum DBIDefaultAttach { + dbidaDocument = 0, + dbidaPhoto = 1, +}; + +struct ConnectionProxy { + ConnectionProxy() : port(0) { + } + QString host; + uint32 port; + QString user, password; +}; + +enum DBIScale { + dbisAuto = 0, + dbisOne = 1, + dbisOneAndQuarter = 2, + dbisOneAndHalf = 3, + dbisTwo = 4, + + dbisScaleCount = 5, +}; + +static const int MatrixRowShift = 40000; + +enum DBIEmojiTab { + dbietRecent = -1, + dbietPeople = 0, + dbietNature = 1, + dbietFood = 2, + dbietActivity = 3, + dbietTravel = 4, + dbietObjects = 5, + dbietSymbols = 6, + dbietStickers = 666, +}; +static const int emojiTabCount = 8; +inline DBIEmojiTab emojiTabAtIndex(int index) { + return (index < 0 || index >= emojiTabCount) ? dbietRecent : DBIEmojiTab(index - 1); +} + +enum DBIPlatform { + dbipWindows = 0, + dbipMac = 1, + dbipLinux64 = 2, + dbipLinux32 = 3, + dbipMacOld = 4, +}; + +enum DBIPeerReportSpamStatus { + dbiprsNoButton = 0, // hidden, but not in the cloud settings yet + dbiprsUnknown = 1, // contacts not loaded yet + dbiprsShowButton = 2, // show report spam button, each show peer request setting from cloud + dbiprsReportSent = 3, // report sent, but the report spam panel is not hidden yet + dbiprsHidden = 4, // hidden in the cloud or not needed (bots, contacts, etc), no more requests + dbiprsRequesting = 5, // requesting the cloud setting right now +}; + +typedef enum { + HitTestNone = 0, + HitTestClient, + HitTestSysButton, + HitTestIcon, + HitTestCaption, + HitTestTop, + HitTestTopRight, + HitTestRight, + HitTestBottomRight, + HitTestBottom, + HitTestBottomLeft, + HitTestLeft, + HitTestTopLeft, +} HitTestType; + +inline QString strMakeFromLetters(const uint32 *letters, int32 len) { + QString result; + result.reserve(len); + for (int32 i = 0; i < len; ++i) { + result.push_back(QChar((((letters[i] << 16) & 0xFF) >> 8) | (letters[i] & 0xFF))); + } + return result; +} + +class MimeType { +public: + + enum TypeEnum { + Unknown, + WebP, + }; + + MimeType(const QMimeType &type) : _typeStruct(type), _type(Unknown) { + } + MimeType(TypeEnum type) : _type(type) { + } + QStringList globPatterns() const; + QString filterString() const; + QString name() const; + +private: + + QMimeType _typeStruct; + TypeEnum _type; + +}; + +MimeType mimeTypeForName(const QString &mime); +MimeType mimeTypeForFile(const QFileInfo &file); +MimeType mimeTypeForData(const QByteArray &data); + +#include + +inline int rowscount(int fullCount, int countPerRow) { + return (fullCount + countPerRow - 1) / countPerRow; +} +inline int floorclamp(int value, int step, int lowest, int highest) { + return qMin(qMax(value / step, lowest), highest); +} +inline int floorclamp(float64 value, int step, int lowest, int highest) { + return qMin(qMax(static_cast(std::floor(value / step)), lowest), highest); +} +inline int ceilclamp(int value, int step, int lowest, int highest) { + return qMax(qMin((value + step - 1) / step, highest), lowest); +} +inline int ceilclamp(float64 value, int32 step, int32 lowest, int32 highest) { + return qMax(qMin(static_cast(std::ceil(value / step)), highest), lowest); +} + +enum ForwardWhatMessages { + ForwardSelectedMessages, + ForwardContextMessage, + ForwardPressedMessage, + ForwardPressedLinkMessage +}; + +enum ShowLayerOption { + CloseOtherLayers = 0x00, + KeepOtherLayers = 0x01, + ShowAfterOtherLayers = 0x03, + + AnimatedShowLayer = 0x00, + ForceFastShowLayer = 0x04, +}; +typedef QFlags ShowLayerOptions; + +static int32 FullArcLength = 360 * 16; +static int32 QuarterArcLength = (FullArcLength / 4); +static int32 MinArcLength = (FullArcLength / 360); +static int32 AlmostFullArcLength = (FullArcLength - MinArcLength); + +template +class RefPairImplementation { +public: + template + const RefPairImplementation &operator=(const RefPairImplementation &other) const { + _first = other._first; + _second = other._second; + return *this; + } + + template + const RefPairImplementation &operator=(const QPair &other) const { + _first = other.first; + _second = other.second; + return *this; + } + +private: + RefPairImplementation(T1 &first, T2 &second) : _first(first), _second(second) { + } + RefPairImplementation(const RefPairImplementation &other); + + template + friend RefPairImplementation RefPairCreator(T3 &first, T4 &second); + + T1 &_first; + T2 &_second; +}; + +template +inline RefPairImplementation RefPairCreator(T1 &first, T2 &second) { + return RefPairImplementation(first, second); +} + +#define RefPair(Type1, Name1, Type2, Name2) Type1 Name1; Type2 Name2; RefPairCreator(Name1, Name2) + +template +class UniquePointer { +public: + explicit UniquePointer(T *p = nullptr) : _p(p) { + } + UniquePointer(const UniquePointer &other) = delete; + UniquePointer &operator=(const UniquePointer &other) = delete; + UniquePointer(UniquePointer &&other) : _p(other.release()) { + } + UniquePointer &operator=(UniquePointer &&other) { + std::swap(_p, other._p); + return *this; + } + template + UniquePointer(UniquePointer &&other) : _p(other.release()) { + } + T *data() const { + return _p; + } + T *release() { + return getPointerAndReset(_p); + } + void reset(T *p = nullptr) { + *this = UniquePointer(p); + } + bool isNull() const { + return data() == nullptr; + } + + void clear() { + reset(); + } + T *operator->() const { + return data(); + } + T &operator*() const { + t_assert(!isNull()); + return *data(); + } + explicit operator bool() const { + return !isNull(); + } + ~UniquePointer() { + delete data(); + } + +private: + T *_p; + +}; +template +inline UniquePointer MakeUnique(Args&&... args) { + return UniquePointer(new T(std_::forward(args)...)); +} + +template +inline QSharedPointer MakeShared(Args&&... args) { + return QSharedPointer(new T(std_::forward(args)...)); +} + +// This pointer is used for global non-POD variables that are allocated +// on demand by createIfNull(lambda) and are never automatically freed. +template +class NeverFreedPointer { +public: + explicit NeverFreedPointer() { + } + NeverFreedPointer(const NeverFreedPointer &other) = delete; + NeverFreedPointer &operator=(const NeverFreedPointer &other) = delete; + + template + void createIfNull(U creator) { + if (isNull()) { + reset(creator()); + } + } + + template + void makeIfNull(Args&&... args) { + if (isNull()) { + reset(new T(std::forward(args)...)); + } + }; + + T *data() const { + return _p; + } + T *release() { + return getPointerAndReset(_p); + } + void reset(T *p = nullptr) { + delete _p; + _p = p; + } + bool isNull() const { + return data() == nullptr; + } + + void clear() { + reset(); + } + T *operator->() const { + return data(); + } + T &operator*() const { + t_assert(!isNull()); + return *data(); + } + explicit operator bool() const { + return !isNull(); + } + +private: + T *_p = nullptr; + +}; + +// This pointer is used for static non-POD variables that are allocated +// on first use by constructor and are never automatically freed. +template +class StaticNeverFreedPointer { +public: + explicit StaticNeverFreedPointer(T *p) : _p(p) { + } + StaticNeverFreedPointer(const StaticNeverFreedPointer &other) = delete; + StaticNeverFreedPointer &operator=(const StaticNeverFreedPointer &other) = delete; + + T *data() const { + return _p; + } + T *release() { + return getPointerAndReset(_p); + } + void reset(T *p = nullptr) { + delete _p; + _p = p; + } + bool isNull() const { + return data() == nullptr; + } + + void clear() { + reset(); + } + T *operator->() const { + return data(); + } + T &operator*() const { + t_assert(!isNull()); + return *data(); + } + explicit operator bool() const { + return !isNull(); + } + +private: + T *_p = nullptr; + +}; + +template +inline void destroyImplementation(I *&ptr) { + if (ptr) { + ptr->destroy(); + ptr = 0; + } + deleteAndMark(ptr); +} + +class Composer; +typedef void(*ComponentConstruct)(void *location, Composer *composer); +typedef void(*ComponentDestruct)(void *location); +typedef void(*ComponentMove)(void *location, void *waslocation); + +struct ComponentWrapStruct { + // don't init any fields, because it is only created in + // global scope, so it will be filled by zeros from the start + ComponentWrapStruct() { + } + ComponentWrapStruct(int size, ComponentConstruct construct, ComponentDestruct destruct, ComponentMove move) + : Size(size) + , Construct(construct) + , Destruct(destruct) + , Move(move) { + } + int Size; + ComponentConstruct Construct; + ComponentDestruct Destruct; + ComponentMove Move; +}; + +template +struct CeilDivideMinimumOne { + static const int Result = ((Value / Denominator) + ((!Value || (Value % Denominator)) ? 1 : 0)); +}; + +extern ComponentWrapStruct ComponentWraps[64]; +extern QAtomicInt ComponentIndexLast; + +template +struct BaseComponent { + BaseComponent() { + } + BaseComponent(const BaseComponent &other) = delete; + BaseComponent &operator=(const BaseComponent &other) = delete; + BaseComponent(BaseComponent &&other) = delete; + BaseComponent &operator=(BaseComponent &&other) = default; + + static int Index() { + static QAtomicInt _index(0); + if (int index = _index.loadAcquire()) { + return index - 1; + } + while (true) { + int last = ComponentIndexLast.loadAcquire(); + if (ComponentIndexLast.testAndSetOrdered(last, last + 1)) { + t_assert(last < 64); + if (_index.testAndSetOrdered(0, last + 1)) { + ComponentWraps[last] = ComponentWrapStruct( + CeilDivideMinimumOne::Result * sizeof(uint64), + Type::ComponentConstruct, Type::ComponentDestruct, Type::ComponentMove); + } + break; + } + } + return _index.loadAcquire() - 1; + } + static uint64 Bit() { + return (1ULL << Index()); + } + +protected: + static void ComponentConstruct(void *location, Composer *composer) { + new (location) Type(); + } + static void ComponentDestruct(void *location) { + ((Type*)location)->~Type(); + } + static void ComponentMove(void *location, void *waslocation) { + *(Type*)location = std_::move(*(Type*)waslocation); + } + +}; + +class ComposerMetadata { +public: + + ComposerMetadata(uint64 mask) : size(0), last(64), _mask(mask) { + for (int i = 0; i < 64; ++i) { + uint64 m = (1 << i); + if (_mask & m) { + int s = ComponentWraps[i].Size; + if (s) { + offsets[i] = size; + size += s; + } else { + offsets[i] = -1; + } + } else if (_mask < m) { + last = i; + for (; i < 64; ++i) { + offsets[i] = -1; + } + } else { + offsets[i] = -1; + } + } + } + + int size, last; + int offsets[64]; + + bool equals(uint64 mask) const { + return _mask == mask; + } + uint64 maskadd(uint64 mask) const { + return _mask | mask; + } + uint64 maskremove(uint64 mask) const { + return _mask & (~mask); + } + +private: + uint64 _mask; + +}; + +const ComposerMetadata *GetComposerMetadata(uint64 mask); + +class Composer { +public: + + Composer(uint64 mask = 0) : _data(zerodata()) { + if (mask) { + const ComposerMetadata *meta = GetComposerMetadata(mask); + int size = sizeof(meta) + meta->size; + void *data = operator new(size); + if (!data) { // terminate if we can't allocate memory + throw "Can't allocate memory!"; + } + + _data = data; + _meta() = meta; + for (int i = 0; i < meta->last; ++i) { + int offset = meta->offsets[i]; + if (offset >= 0) { + try { + ComponentWraps[i].Construct(_dataptrunsafe(offset), this); + } catch (...) { + while (i > 0) { + --i; + offset = meta->offsets[--i]; + if (offset >= 0) { + ComponentWraps[i].Destruct(_dataptrunsafe(offset)); + } + } + throw; + } + } + } + } + } + Composer(const Composer &other) = delete; + Composer &operator=(const Composer &other) = delete; + ~Composer() { + if (_data != zerodata()) { + const ComposerMetadata *meta = _meta(); + for (int i = 0; i < meta->last; ++i) { + int offset = meta->offsets[i]; + if (offset >= 0) { + ComponentWraps[i].Destruct(_dataptrunsafe(offset)); + } + } + operator delete(_data); + } + } + + template + bool Has() const { + return (_meta()->offsets[Type::Index()] >= 0); + } + + template + Type *Get() { + return static_cast(_dataptr(_meta()->offsets[Type::Index()])); + } + template + const Type *Get() const { + return static_cast(_dataptr(_meta()->offsets[Type::Index()])); + } + +protected: + void UpdateComponents(uint64 mask = 0) { + if (!_meta()->equals(mask)) { + Composer tmp(mask); + tmp.swap(*this); + if (_data != zerodata() && tmp._data != zerodata()) { + const ComposerMetadata *meta = _meta(), *wasmeta = tmp._meta(); + for (int i = 0; i < meta->last; ++i) { + int offset = meta->offsets[i], wasoffset = wasmeta->offsets[i]; + if (offset >= 0 && wasoffset >= 0) { + ComponentWraps[i].Move(_dataptrunsafe(offset), tmp._dataptrunsafe(wasoffset)); + } + } + } + } + } + void AddComponents(uint64 mask = 0) { + UpdateComponents(_meta()->maskadd(mask)); + } + void RemoveComponents(uint64 mask = 0) { + UpdateComponents(_meta()->maskremove(mask)); + } + +private: + static const ComposerMetadata *ZeroComposerMetadata; + static void *zerodata() { + return &ZeroComposerMetadata; + } + + void *_dataptrunsafe(int skip) const { + return (char*)_data + sizeof(_meta()) + skip; + } + void *_dataptr(int skip) const { + return (skip >= 0) ? _dataptrunsafe(skip) : 0; + } + const ComposerMetadata *&_meta() const { + return *static_cast(_data); + } + void *_data; + + void swap(Composer &other) { + std::swap(_data, other._data); + } + +}; + +template +class SharedCallback { +public: + virtual R call(Args... args) const = 0; + virtual ~SharedCallback() { + } + typedef QSharedPointer> Ptr; +}; + +template +class FunctionImplementation { +public: + virtual R call() = 0; + virtual void destroy() { delete this; } + virtual ~FunctionImplementation() {} +}; +template +class NullFunctionImplementation : public FunctionImplementation { +public: + virtual R call() { return R(); } + virtual void destroy() {} + static NullFunctionImplementation SharedInstance; +}; +template +NullFunctionImplementation NullFunctionImplementation::SharedInstance; +template +class FunctionCreator { +public: + FunctionCreator(FunctionImplementation *ptr) : _ptr(ptr) {} + FunctionCreator(const FunctionCreator &other) : _ptr(other.create()) {} + FunctionImplementation *create() const { return getPointerAndReset(_ptr); } + ~FunctionCreator() { destroyImplementation(_ptr); } +private: + FunctionCreator &operator=(const FunctionCreator &other); + mutable FunctionImplementation *_ptr; +}; +template +class Function { +public: + typedef FunctionCreator Creator; + static Creator Null() { return Creator(&NullFunctionImplementation::SharedInstance); } + Function(const Creator &creator) : _implementation(creator.create()) {} + R call() { return _implementation->call(); } + ~Function() { destroyImplementation(_implementation); } +private: + Function(const Function &other); + Function &operator=(const Function &other); + FunctionImplementation *_implementation; +}; + +template +class WrappedFunction : public FunctionImplementation { +public: + typedef R(*Method)(); + WrappedFunction(Method method) : _method(method) {} + virtual R call() { return (*_method)(); } +private: + Method _method; +}; +template +inline FunctionCreator func(R(*method)()) { + return FunctionCreator(new WrappedFunction(method)); +} +template +class ObjectFunction : public FunctionImplementation { +public: + typedef R(I::*Method)(); + ObjectFunction(O *obj, Method method) : _obj(obj), _method(method) {} + virtual R call() { return (_obj->*_method)(); } +private: + O *_obj; + Method _method; +}; +template +inline FunctionCreator func(O *obj, R(I::*method)()) { + return FunctionCreator(new ObjectFunction(obj, method)); +} + +template +class Function1Implementation { +public: + virtual R call(A1 a1) = 0; + virtual void destroy() { delete this; } + virtual ~Function1Implementation() {} +}; +template +class NullFunction1Implementation : public Function1Implementation { +public: + virtual R call(A1 a1) { return R(); } + virtual void destroy() {} + static NullFunction1Implementation SharedInstance; +}; +template +NullFunction1Implementation NullFunction1Implementation::SharedInstance; +template +class Function1Creator { +public: + Function1Creator(Function1Implementation *ptr) : _ptr(ptr) {} + Function1Creator(const Function1Creator &other) : _ptr(other.create()) {} + Function1Implementation *create() const { return getPointerAndReset(_ptr); } + ~Function1Creator() { destroyImplementation(_ptr); } +private: + Function1Creator &operator=(const Function1Creator &other); + mutable Function1Implementation *_ptr; +}; +template +class Function1 { +public: + typedef Function1Creator Creator; + static Creator Null() { return Creator(&NullFunction1Implementation::SharedInstance); } + Function1(const Creator &creator) : _implementation(creator.create()) {} + R call(A1 a1) { return _implementation->call(a1); } + ~Function1() { _implementation->destroy(); } +private: + Function1(const Function1 &other); + Function1 &operator=(const Function1 &other); + Function1Implementation *_implementation; +}; + +template +class WrappedFunction1 : public Function1Implementation { +public: + typedef R(*Method)(A1); + WrappedFunction1(Method method) : _method(method) {} + virtual R call(A1 a1) { return (*_method)(a1); } +private: + Method _method; +}; +template +inline Function1Creator func(R(*method)(A1)) { + return Function1Creator(new WrappedFunction1(method)); +} +template +class ObjectFunction1 : public Function1Implementation { +public: + typedef R(I::*Method)(A1); + ObjectFunction1(O *obj, Method method) : _obj(obj), _method(method) {} + virtual R call(A1 a1) { return (_obj->*_method)(a1); } +private: + O *_obj; + Method _method; +}; +template +Function1Creator func(O *obj, R(I::*method)(A1)) { + return Function1Creator(new ObjectFunction1(obj, method)); +} + +template +class Function2Implementation { +public: + virtual R call(A1 a1, A2 a2) = 0; + virtual void destroy() { delete this; } + virtual ~Function2Implementation() {} +}; +template +class NullFunction2Implementation : public Function2Implementation { +public: + virtual R call(A1 a1, A2 a2) { return R(); } + virtual void destroy() {} + static NullFunction2Implementation SharedInstance; +}; +template +NullFunction2Implementation NullFunction2Implementation::SharedInstance; +template +class Function2Creator { +public: + Function2Creator(Function2Implementation *ptr) : _ptr(ptr) {} + Function2Creator(const Function2Creator &other) : _ptr(other.create()) {} + Function2Implementation *create() const { return getPointerAndReset(_ptr); } + ~Function2Creator() { destroyImplementation(_ptr); } +private: + Function2Creator &operator=(const Function2Creator &other); + mutable Function2Implementation *_ptr; +}; +template +class Function2 { +public: + typedef Function2Creator Creator; + static Creator Null() { return Creator(&NullFunction2Implementation::SharedInstance); } + Function2(const Creator &creator) : _implementation(creator.create()) {} + R call(A1 a1, A2 a2) { return _implementation->call(a1, a2); } + ~Function2() { destroyImplementation(_implementation); } +private: + Function2(const Function2 &other); + Function2 &operator=(const Function2 &other); + Function2Implementation *_implementation; +}; + +template +class WrappedFunction2 : public Function2Implementation { +public: + typedef R(*Method)(A1, A2); + WrappedFunction2(Method method) : _method(method) {} + virtual R call(A1 a1, A2 a2) { return (*_method)(a1, a2); } +private: + Method _method; +}; +template +Function2Creator func(R(*method)(A1, A2)) { + return Function2Creator(new WrappedFunction2(method)); +} + +template +class ObjectFunction2 : public Function2Implementation { +public: + typedef R(I::*Method)(A1, A2); + ObjectFunction2(O *obj, Method method) : _obj(obj), _method(method) {} + virtual R call(A1 a1, A2 a2) { return (_obj->*_method)(a1, a2); } +private: + O *_obj; + Method _method; +}; +template +Function2Creator func(O *obj, R(I::*method)(A1, A2)) { + return Function2Creator(new ObjectFunction2(obj, method)); +} diff --git a/Telegram/SourceFiles/boxes/aboutbox.cpp b/Telegram/SourceFiles/boxes/aboutbox.cpp index a6bad679e4..da41b067e7 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.cpp +++ b/Telegram/SourceFiles/boxes/aboutbox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" @@ -44,6 +44,8 @@ AboutBox::AboutBox() : AbstractBox(st::aboutWidth) connect(&_done, SIGNAL(clicked()), this, SLOT(onClose())); prepare(); + + setAcceptDrops(true); } void AboutBox::hideAll() { @@ -82,7 +84,7 @@ void AboutBox::onVersion() { } url = url.arg(qsl("tbeta%1_%2").arg(cRealBetaVersion()).arg(countBetaVersionSignature(cRealBetaVersion()))); - App::app()->clipboard()->setText(url); + Application::clipboard()->setText(url); Ui::showLayer(new InformBox("The link to the current private beta version of Telegram Desktop was copied to the clipboard.")); } else { @@ -105,10 +107,39 @@ void AboutBox::paintEvent(QPaintEvent *e) { paintTitle(p, qsl("Telegram Desktop")); } +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS +QString _getCrashReportFile(const QMimeData *m) { + if (!m || m->urls().size() != 1) return QString(); + + QString file(m->urls().at(0).toLocalFile()); + if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file); + + return file.endsWith(qstr(".telegramcrash"), Qt::CaseInsensitive) ? file : QString(); +} +#endif // !TDESKTOP_DISABLE_CRASH_REPORTS + +void AboutBox::dragEnterEvent(QDragEnterEvent *e) { +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS + if (!_getCrashReportFile(e->mimeData()).isEmpty()) { + e->setDropAction(Qt::CopyAction); + e->accept(); + } +#endif +} + +void AboutBox::dropEvent(QDropEvent *e) { +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS + if (!_getCrashReportFile(e->mimeData()).isEmpty()) { + e->acceptProposedAction(); + showCrashReportWindow(_getCrashReportFile(e->mimeData())); + } +#endif // !TDESKTOP_DISABLE_CRASH_REPORTS +} + QString telegramFaqLink() { QString result = qsl("https://telegram.org/faq"); if (cLang() > languageDefault && cLang() < languageCount) { - const char *code = LanguageCodes[cLang()]; + const char *code = LanguageCodes[cLang()].c_str(); if (qstr("de") == code || qstr("es") == code || qstr("it") == code || qstr("ko") == code) { result += qsl("/") + code; } else if (qstr("pt_BR") == code) { @@ -116,4 +147,4 @@ QString telegramFaqLink() { } } return result; -} \ No newline at end of file +} diff --git a/Telegram/SourceFiles/boxes/aboutbox.h b/Telegram/SourceFiles/boxes/aboutbox.h index 533ab03233..ceccef089f 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.h +++ b/Telegram/SourceFiles/boxes/aboutbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -31,7 +31,10 @@ public: void resizeEvent(QResizeEvent *e); void keyPressEvent(QKeyEvent *e); void paintEvent(QPaintEvent *e); - + + void dragEnterEvent(QDragEnterEvent *e); + void dropEvent(QDropEvent *e); + public slots: void onVersion(); diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp index e61b40fcee..e9d1783ad5 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.cpp +++ b/Telegram/SourceFiles/boxes/abstractbox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" @@ -178,6 +178,14 @@ void AbstractBox::resizeMaxHeight(int32 newWidth, int32 maxHeight) { _maxHeight = maxHeight; resize(newWidth, countHeight()); if (parentWidget()) { + QRect r = geometry(); + int32 parenth = parentWidget()->height(); + if (r.top() + r.height() + st::boxVerticalMargin > parenth) { + int32 newTop = qMax(parenth - int(st::boxVerticalMargin) - r.height(), (parenth - r.height()) / 2); + if (newTop != r.top()) { + move(r.left(), newTop); + } + } parentWidget()->update(geometry().united(g).marginsAdded(QMargins(st::boxShadow.pxWidth(), st::boxShadow.pxHeight(), st::boxShadow.pxWidth(), st::boxShadow.pxHeight()))); } } diff --git a/Telegram/SourceFiles/boxes/abstractbox.h b/Telegram/SourceFiles/boxes/abstractbox.h index 4920cebd13..14c3f3d741 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.h +++ b/Telegram/SourceFiles/boxes/abstractbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 3ba4eac434..53d9e5d808 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" @@ -188,11 +188,11 @@ void AddContactBox::onSave() { } _sentName = firstName; if (_user) { - _contactId = MTP::nonce(); + _contactId = rand_value(); QVector v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(_user->phone), MTP_string(firstName), MTP_string(lastName))); _addRequest = MTP::send(MTPcontacts_ImportContacts(MTP_vector(v), MTP_bool(false)), rpcDone(&AddContactBox::onSaveUserDone), rpcFail(&AddContactBox::onSaveUserFail)); } else { - _contactId = MTP::nonce(); + _contactId = rand_value(); QVector v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(phone), MTP_string(firstName), MTP_string(lastName))); _addRequest = MTP::send(MTPcontacts_ImportContacts(MTP_vector(v), MTP_bool(false)), rpcDone(&AddContactBox::onImportDone)); } @@ -224,18 +224,15 @@ void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) { App::feedUsers(d.vusers); const QVector &v(d.vimported.c_vector().v); - int32 uid = 0; + UserData *user = nullptr; if (!v.isEmpty()) { const MTPDimportedContact &c(v.front().c_importedContact()); if (c.vclient_id.v != _contactId) return; - uid = c.vuser_id.v; - if (uid && !App::userLoaded(uid)) { - uid = 0; - } + user = App::userLoaded(c.vuser_id.v); } - if (uid) { - Notify::userIsContactChanged(App::userLoaded(peerFromUser(uid)), true); + if (user) { + Notify::userIsContactChanged(user, true); Ui::hideLayer(); } else { _save.hide(); @@ -275,7 +272,7 @@ NewGroupBox::NewGroupBox() : AbstractBox(), _group(this, qsl("group_type"), 0, lang(lng_create_group_title), true), _channel(this, qsl("group_type"), 1, lang(lng_create_channel_title)), _aboutGroupWidth(width() - st::boxPadding.left() - st::boxButtonPadding.right() - st::newGroupPadding.left() - st::defaultRadiobutton.textPosition.x()), -_aboutGroup(st::normalFont, lng_create_group_about(lt_count, cMaxGroupCount()), _defaultOptions, _aboutGroupWidth), +_aboutGroup(st::normalFont, lng_create_group_about(lt_count, Global::MegagroupSizeMax()), _defaultOptions, _aboutGroupWidth), _aboutChannel(st::normalFont, lang(lng_create_channel_about), _defaultOptions, _aboutGroupWidth), _next(this, lang(lng_create_group_next), st::defaultBoxButton), _cancel(this, lang(lng_cancel), st::cancelBoxButton) { @@ -499,8 +496,8 @@ void GroupInfoBox::onNext() { Ui::showLayer(new ContactsBox(title, _photoBig), KeepOtherLayers); } else { bool mega = false; - int32 flags = mega ? MTPchannels_CreateChannel::flag_megagroup : MTPchannels_CreateChannel::flag_broadcast; - _creationRequestId = MTP::send(MTPchannels_CreateChannel(MTP_int(flags), MTP_string(title), MTP_string(description)), rpcDone(&GroupInfoBox::creationDone), rpcFail(&GroupInfoBox::creationFail)); + MTPchannels_CreateChannel::Flags flags = mega ? MTPchannels_CreateChannel::Flag::f_megagroup : MTPchannels_CreateChannel::Flag::f_broadcast; + _creationRequestId = MTP::send(MTPchannels_CreateChannel(MTP_flags(flags), MTP_string(title), MTP_string(description)), rpcDone(&GroupInfoBox::creationDone), rpcFail(&GroupInfoBox::creationFail)); } } @@ -540,6 +537,9 @@ bool GroupInfoBox::creationFail(const RPCError &error) { _title.setFocus(); _title.showError(); return true; + } else if (error.type() == qstr("USER_RESTRICTED")) { + Ui::showLayer(new InformBox(lang(lng_cant_do_this))); + return true; } return false; } @@ -605,12 +605,12 @@ void GroupInfoBox::onPhotoReady(const QImage &img) { SetupChannelBox::SetupChannelBox(ChannelData *channel, bool existing) : AbstractBox() , _channel(channel) , _existing(existing) -, _public(this, qsl("channel_privacy"), 0, lang(lng_create_public_channel_title), true) -, _private(this, qsl("channel_privacy"), 1, lang(lng_create_private_channel_title)) +, _public(this, qsl("channel_privacy"), 0, lang(channel->isMegagroup() ? lng_create_public_group_title : lng_create_public_channel_title), true) +, _private(this, qsl("channel_privacy"), 1, lang(channel->isMegagroup() ? lng_create_private_group_title : lng_create_private_channel_title)) , _comments(this, lang(lng_create_channel_comments), false) , _aboutPublicWidth(width() - st::boxPadding.left() - st::boxButtonPadding.right() - st::newGroupPadding.left() - st::defaultRadiobutton.textPosition.x()) -, _aboutPublic(st::normalFont, lang(lng_create_public_channel_about), _defaultOptions, _aboutPublicWidth) -, _aboutPrivate(st::normalFont, lang(lng_create_private_channel_about), _defaultOptions, _aboutPublicWidth) +, _aboutPublic(st::normalFont, lang(channel->isMegagroup() ? lng_create_public_group_about : lng_create_public_channel_about), _defaultOptions, _aboutPublicWidth) +, _aboutPrivate(st::normalFont, lang(channel->isMegagroup() ? lng_create_private_group_about : lng_create_private_channel_about), _defaultOptions, _aboutPublicWidth) , _aboutComments(st::normalFont, lang(lng_create_channel_comments_about), _defaultOptions, _aboutPublicWidth) , _link(this, st::defaultInputField, QString(), channel->username, true) , _linkOver(false) @@ -626,7 +626,7 @@ SetupChannelBox::SetupChannelBox(ChannelData *channel, bool existing) : Abstract _checkRequestId = MTP::send(MTPchannels_CheckUsername(_channel->inputChannel, MTP_string("preston")), RPCDoneHandlerPtr(), rpcFail(&SetupChannelBox::onFirstCheckFail)); _aboutPublicHeight = _aboutPublic.countHeight(_aboutPublicWidth); - setMaxHeight(st::boxPadding.top() + st::newGroupPadding.top() + _public.height() + _aboutPublicHeight + st::newGroupSkip + _private.height() + _aboutPrivate.countHeight(_aboutPublicWidth)/* + st::newGroupSkip + _comments.height() + _aboutComments.countHeight(_aboutPublicWidth)*/ + st::newGroupSkip + st::newGroupPadding.bottom() + st::newGroupLinkPadding.top() + _link.height() + st::newGroupLinkPadding.bottom() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom()); + updateMaxHeight(); connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); connect(&_skip, SIGNAL(clicked()), this, SLOT(onClose())); @@ -669,6 +669,14 @@ void SetupChannelBox::showDone() { _link.setFocus(); } +void SetupChannelBox::updateMaxHeight() { + if (!_channel->isMegagroup() || _public.checked()) { + setMaxHeight(st::boxPadding.top() + st::newGroupPadding.top() + _public.height() + _aboutPublicHeight + st::newGroupSkip + _private.height() + _aboutPrivate.countHeight(_aboutPublicWidth)/* + st::newGroupSkip + _comments.height() + _aboutComments.countHeight(_aboutPublicWidth)*/ + st::newGroupSkip + st::newGroupPadding.bottom() + st::newGroupLinkPadding.top() + _link.height() + st::newGroupLinkPadding.bottom() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom()); + } else { + setMaxHeight(st::boxPadding.top() + st::newGroupPadding.top() + _public.height() + _aboutPublicHeight + st::newGroupSkip + _private.height() + _aboutPrivate.countHeight(_aboutPublicWidth)/* + st::newGroupSkip + _comments.height() + _aboutComments.countHeight(_aboutPublicWidth)*/ + st::newGroupSkip + st::newGroupPadding.bottom() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom()); + } +} + void SetupChannelBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { if (_link.hasFocus()) { @@ -699,22 +707,26 @@ void SetupChannelBox::paintEvent(QPaintEvent *e) { //QRect aboutComments(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultRadiobutton.textPosition.x(), _comments.y() + _comments.height(), _aboutPublicWidth, _aboutPublicHeight); //_aboutComments.drawLeft(p, aboutComments.x(), aboutComments.y(), aboutComments.width(), width()); - p.setPen(st::black); - p.setFont(st::newGroupLinkFont); - p.drawTextLeft(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultInputField.textMargins.left(), _link.y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop, width(), lang(_link.isHidden() ? lng_create_group_invite_link : lng_create_group_link)); + if (!_channel->isMegagroup() || !_link.isHidden()) { + p.setPen(st::black); + p.setFont(st::newGroupLinkFont); + p.drawTextLeft(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultInputField.textMargins.left(), _link.y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop, width(), lang(_link.isHidden() ? lng_create_group_invite_link : lng_create_group_link)); + } if (_link.isHidden()) { - QTextOption option(style::al_left); - option.setWrapMode(QTextOption::WrapAnywhere); - p.setFont(_linkOver ? st::boxTextFont->underline() : st::boxTextFont); - p.setPen(st::btnDefLink.color); - p.drawText(_invitationLink, _channel->invitationUrl, option); - if (!_goodTextLink.isEmpty() && a_goodOpacity.current() > 0) { - p.setOpacity(a_goodOpacity.current()); - p.setPen(st::setGoodColor); - p.setFont(st::boxTextFont); - p.drawTextRight(st::boxPadding.right(), _link.y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop + st::newGroupLinkFont->ascent - st::boxTextFont->ascent, width(), _goodTextLink); - p.setOpacity(1); + if (!_channel->isMegagroup()) { + QTextOption option(style::al_left); + option.setWrapMode(QTextOption::WrapAnywhere); + p.setFont(_linkOver ? st::boxTextFont->underline() : st::boxTextFont); + p.setPen(st::btnDefLink.color); + p.drawText(_invitationLink, _channel->invitationUrl, option); + if (!_goodTextLink.isEmpty() && a_goodOpacity.current() > 0) { + p.setOpacity(a_goodOpacity.current()); + p.setPen(st::setGoodColor); + p.setFont(st::boxTextFont); + p.drawTextRight(st::boxPadding.right(), _link.y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop + st::newGroupLinkFont->ascent - st::boxTextFont->ascent, width(), _goodTextLink); + p.setOpacity(1); + } } } else { if (!_errorText.isEmpty()) { @@ -750,7 +762,7 @@ void SetupChannelBox::mouseMoveEvent(QMouseEvent *e) { void SetupChannelBox::mousePressEvent(QMouseEvent *e) { mouseMoveEvent(e); if (_linkOver) { - App::app()->clipboard()->setText(_channel->invitationUrl); + Application::clipboard()->setText(_channel->invitationUrl); _goodTextLink = lang(lng_create_channel_link_copied); a_goodOpacity = anim::fvalue(1, 0); _a_goodFade.start(); @@ -827,7 +839,7 @@ void SetupChannelBox::onChange() { } _checkTimer.stop(); } else { - int32 i, len = name.size(); + int32 len = name.size(); for (int32 i = 0; i < len; ++i) { QChar ch = name.at(i); if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '_') { @@ -879,6 +891,9 @@ void SetupChannelBox::onPrivacyChange() { _link.hide(); setFocus(); } + if (_channel->isMegagroup()) { + updateMaxHeight(); + } update(); } @@ -929,7 +944,10 @@ bool SetupChannelBox::onCheckFail(const RPCError &error) { _checkRequestId = 0; QString err(error.type()); - if (err == "CHANNELS_ADMIN_PUBLIC_TOO_MUCH") { + if (err == qstr("CHANNEL_PUBLIC_GROUP_NA")) { + Ui::hideLayer(); + return true; + } else if (err == qstr("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { if (_existing) { Ui::showLayer(new InformBox(lang(lng_channels_too_much_public_existing))); } else { @@ -938,11 +956,11 @@ bool SetupChannelBox::onCheckFail(const RPCError &error) { onPrivacyChange(); } return true; - } else if (err == "USERNAME_INVALID") { + } else if (err == qstr("USERNAME_INVALID")) { _errorText = lang(lng_create_channel_link_invalid); update(); return true; - } else if (err == "USERNAME_OCCUPIED" && _checkUsername != _channel->username) { + } else if (err == qstr("USERNAME_OCCUPIED") && _checkUsername != _channel->username) { _errorText = lang(lng_create_channel_link_occupied); update(); return true; @@ -957,7 +975,10 @@ bool SetupChannelBox::onFirstCheckFail(const RPCError &error) { _checkRequestId = 0; QString err(error.type()); - if (err == "CHANNELS_ADMIN_PUBLIC_TOO_MUCH") { + if (err == qstr("CHANNEL_PUBLIC_GROUP_NA")) { + Ui::hideLayer(); + return true; + } else if (err == qstr("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { if (_existing) { Ui::showLayer(new InformBox(lang(lng_channels_too_much_public_existing))); } else { @@ -1094,7 +1115,8 @@ void EditNameTitleBox::onSave() { } _sentName = first; if (_peer == App::self()) { - _requestId = MTP::send(MTPaccount_UpdateProfile(MTP_string(first), MTP_string(last)), rpcDone(&EditNameTitleBox::onSaveSelfDone), rpcFail(&EditNameTitleBox::onSaveSelfFail)); + MTPaccount_UpdateProfile::Flags flags = MTPaccount_UpdateProfile::Flag::f_first_name | MTPaccount_UpdateProfile::Flag::f_last_name; + _requestId = MTP::send(MTPaccount_UpdateProfile(MTP_flags(flags), MTP_string(first), MTP_string(last), MTPstring()), rpcDone(&EditNameTitleBox::onSaveSelfDone), rpcFail(&EditNameTitleBox::onSaveSelfFail)); } else if (_peer->isChat()) { _requestId = MTP::send(MTPmessages_EditChatTitle(_peer->asChat()->inputChat, MTP_string(first)), rpcDone(&EditNameTitleBox::onSaveChatDone), rpcFail(&EditNameTitleBox::onSaveChatFail)); } @@ -1150,14 +1172,17 @@ void EditNameTitleBox::onSaveChatDone(const MTPUpdates &updates) { emit closed(); } -EditChannelBox::EditChannelBox(ChannelData *channel) : -_channel(channel), -_save(this, lang(lng_settings_save), st::defaultBoxButton), -_cancel(this, lang(lng_cancel), st::cancelBoxButton), -_title(this, st::defaultInputField, lang(lng_dlg_new_channel_name), _channel->name), -_description(this, st::newGroupDescription, lang(lng_create_group_description), _channel->about), -_publicLink(this, lang(channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link), st::defaultBoxLinkButton), -_saveTitleRequestId(0), _saveDescriptionRequestId(0) { +EditChannelBox::EditChannelBox(ChannelData *channel) : AbstractBox() +, _channel(channel) +, _save(this, lang(lng_settings_save), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _title(this, st::defaultInputField, lang(lng_dlg_new_channel_name), _channel->name) +, _description(this, st::newGroupDescription, lang(lng_create_group_description), _channel->about) +, _sign(this, lang(lng_edit_sign_messages), channel->addsSignature()) +, _publicLink(this, lang(channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link), st::defaultBoxLinkButton) +, _saveTitleRequestId(0) +, _saveDescriptionRequestId(0) +, _saveSignRequestId(0) { connect(App::main(), SIGNAL(peerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&)), this, SLOT(peerUpdated(PeerData*))); setMouseTracking(true); @@ -1183,6 +1208,7 @@ _saveTitleRequestId(0), _saveDescriptionRequestId(0) { void EditChannelBox::hideAll() { _title.hide(); _description.hide(); + _sign.hide(); _save.hide(); _cancel.hide(); _publicLink.hide(); @@ -1193,10 +1219,15 @@ void EditChannelBox::showAll() { _description.show(); _save.show(); _cancel.show(); - if (_channel->isMegagroup()) { - _publicLink.hide(); - } else { + if (_channel->canEditUsername()) { _publicLink.show(); + } else { + _publicLink.hide(); + } + if (_channel->isMegagroup()) { + _sign.hide(); + } else { + _sign.show(); } } @@ -1224,6 +1255,7 @@ void EditChannelBox::paintEvent(QPaintEvent *e) { void EditChannelBox::peerUpdated(PeerData *peer) { if (peer == _channel) { _publicLink.setText(lang(_channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link)); + _sign.setChecked(_channel->addsSignature()); } } @@ -1235,7 +1267,12 @@ void EditChannelBox::onDescriptionResized() { void EditChannelBox::updateMaxHeight() { int32 h = st::boxTitleHeight + st::newGroupInfoPadding.top() + _title.height(); h += st::newGroupDescriptionPadding.top() + _description.height() + st::newGroupDescriptionPadding.bottom(); - h += st::newGroupPublicLinkPadding.top() + _publicLink.height() + st::newGroupPublicLinkPadding.bottom(); + if (!_channel->isMegagroup()) { + h += st::newGroupPublicLinkPadding.top() + _sign.height() + st::newGroupPublicLinkPadding.bottom(); + } + if (_channel->canEditUsername()) { + h += st::newGroupPublicLinkPadding.top() + _publicLink.height() + st::newGroupPublicLinkPadding.bottom(); + } h += st::boxPadding.bottom() + st::newGroupInfoPadding.bottom() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom(); setMaxHeight(h); } @@ -1246,14 +1283,20 @@ void EditChannelBox::resizeEvent(QResizeEvent *e) { _description.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _title.y() + _title.height() + st::newGroupDescriptionPadding.top()); - _publicLink.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _description.y() + _description.height() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); + _sign.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _description.y() + _description.height() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); + + if (_channel->isMegagroup()) { + _publicLink.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _description.y() + _description.height() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); + } else { + _publicLink.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _sign.y() + _sign.height() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); + } _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); _cancel.moveToRight(st::boxButtonPadding.right() + _save.width() + st::boxButtonPadding.left(), _save.y()); } void EditChannelBox::onSave() { - if (_saveTitleRequestId || _saveDescriptionRequestId) return; + if (_saveTitleRequestId || _saveDescriptionRequestId || _saveSignRequestId) return; QString title = prepareText(_title.getLastText()), description = prepareText(_description.getLastText(), true); if (title.isEmpty()) { @@ -1263,7 +1306,11 @@ void EditChannelBox::onSave() { } _sentTitle = title; _sentDescription = description; - _saveTitleRequestId = MTP::send(MTPchannels_EditTitle(_channel->inputChannel, MTP_string(_sentTitle)), rpcDone(&EditChannelBox::onSaveTitleDone), rpcFail(&EditChannelBox::onSaveFail)); + if (_sentTitle == _channel->name) { + saveDescription(); + } else { + _saveTitleRequestId = MTP::send(MTPchannels_EditTitle(_channel->inputChannel, MTP_string(_sentTitle)), rpcDone(&EditChannelBox::onSaveTitleDone), rpcFail(&EditChannelBox::onSaveFail)); + } } void EditChannelBox::onPublicLink() { @@ -1271,7 +1318,19 @@ void EditChannelBox::onPublicLink() { } void EditChannelBox::saveDescription() { - _saveDescriptionRequestId = MTP::send(MTPchannels_EditAbout(_channel->inputChannel, MTP_string(_sentDescription)), rpcDone(&EditChannelBox::onSaveDescriptionDone), rpcFail(&EditChannelBox::onSaveFail)); + if (_sentDescription == _channel->about) { + saveSign(); + } else { + _saveDescriptionRequestId = MTP::send(MTPchannels_EditAbout(_channel->inputChannel, MTP_string(_sentDescription)), rpcDone(&EditChannelBox::onSaveDescriptionDone), rpcFail(&EditChannelBox::onSaveFail)); + } +} + +void EditChannelBox::saveSign() { + if (_channel->isMegagroup() || _channel->addsSignature() == _sign.checked()) { + onClose(); + } else { + _saveSignRequestId = MTP::send(MTPchannels_ToggleSignatures(_channel->inputChannel, MTP_bool(_sign.checked())), rpcDone(&EditChannelBox::onSaveSignDone), rpcFail(&EditChannelBox::onSaveFail)); + } } bool EditChannelBox::onSaveFail(const RPCError &error, mtpRequestId req) { @@ -1295,24 +1354,46 @@ bool EditChannelBox::onSaveFail(const RPCError &error, mtpRequestId req) { _saveDescriptionRequestId = 0; if (err == qstr("CHAT_ABOUT_NOT_MODIFIED")) { _channel->about = _sentDescription; - if (App::api()) emit App::api()->fullPeerUpdated(_channel); - onClose(); + if (App::api()) { + emit App::api()->fullPeerUpdated(_channel); + } + saveSign(); + return true; } else { _description.setFocus(); } + } else if (req == _saveSignRequestId) { + _saveSignRequestId = 0; + if (err == qstr("CHAT_NOT_MODIFIED")) { + onClose(); + return true; + } } return true; } void EditChannelBox::onSaveTitleDone(const MTPUpdates &updates) { _saveTitleRequestId = 0; - App::main()->sentUpdatesReceived(updates); + if (App::main()) { + App::main()->sentUpdatesReceived(updates); + } saveDescription(); } void EditChannelBox::onSaveDescriptionDone(const MTPBool &result) { _saveDescriptionRequestId = 0; _channel->about = _sentDescription; - if (App::api()) emit App::api()->fullPeerUpdated(_channel); + if (App::api()) { + emit App::api()->fullPeerUpdated(_channel); + } + saveSign(); +} + +void EditChannelBox::onSaveSignDone(const MTPUpdates &updates) { + _saveSignRequestId = 0; + if (App::main()) { + App::main()->sentUpdatesReceived(updates); + } onClose(); } + diff --git a/Telegram/SourceFiles/boxes/addcontactbox.h b/Telegram/SourceFiles/boxes/addcontactbox.h index b50c715deb..90e55311c3 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.h +++ b/Telegram/SourceFiles/boxes/addcontactbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -211,6 +211,8 @@ private: bool onCheckFail(const RPCError &error); bool onFirstCheckFail(const RPCError &error); + void updateMaxHeight(); + ChannelData *_channel; bool _existing; @@ -315,18 +317,21 @@ private: void onSaveTitleDone(const MTPUpdates &updates); void onSaveDescriptionDone(const MTPBool &result); + void onSaveSignDone(const MTPUpdates &updates); bool onSaveFail(const RPCError &e, mtpRequestId req); void saveDescription(); + void saveSign(); ChannelData *_channel; BoxButton _save, _cancel; InputField _title; InputArea _description; + Checkbox _sign; LinkButton _publicLink; - mtpRequestId _saveTitleRequestId, _saveDescriptionRequestId; + mtpRequestId _saveTitleRequestId, _saveDescriptionRequestId, _saveSignRequestId; QString _sentTitle, _sentDescription; }; diff --git a/Telegram/SourceFiles/boxes/autolockbox.cpp b/Telegram/SourceFiles/boxes/autolockbox.cpp index 93675868c5..3af3b2615e 100644 --- a/Telegram/SourceFiles/boxes/autolockbox.cpp +++ b/Telegram/SourceFiles/boxes/autolockbox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" diff --git a/Telegram/SourceFiles/boxes/autolockbox.h b/Telegram/SourceFiles/boxes/autolockbox.h index 827e103654..db7035972c 100644 --- a/Telegram/SourceFiles/boxes/autolockbox.h +++ b/Telegram/SourceFiles/boxes/autolockbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/boxes/backgroundbox.cpp b/Telegram/SourceFiles/boxes/backgroundbox.cpp index 098b301c2a..e35eb32c96 100644 --- a/Telegram/SourceFiles/boxes/backgroundbox.cpp +++ b/Telegram/SourceFiles/boxes/backgroundbox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" diff --git a/Telegram/SourceFiles/boxes/backgroundbox.h b/Telegram/SourceFiles/boxes/backgroundbox.h index e2a6e5a820..4d3b5bfef2 100644 --- a/Telegram/SourceFiles/boxes/backgroundbox.h +++ b/Telegram/SourceFiles/boxes/backgroundbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index e6dd0269a9..41a87c7ebd 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" @@ -34,19 +34,19 @@ TextParseOptions _confirmBoxTextOptions = { Qt::LayoutDirectionAuto, // dir }; -ConfirmBox::ConfirmBox(const QString &text, const QString &doneText, const style::BoxButton &doneStyle, const QString &cancelText, const style::BoxButton &cancelStyle) : AbstractBox(st::boxWidth), -_informative(false), -_text(100), -_confirm(this, doneText.isEmpty() ? lang(lng_box_ok) : doneText, doneStyle), -_cancel(this, cancelText.isEmpty() ? lang(lng_cancel) : cancelText, cancelStyle) { +ConfirmBox::ConfirmBox(const QString &text, const QString &doneText, const style::BoxButton &doneStyle, const QString &cancelText, const style::BoxButton &cancelStyle) : AbstractBox(st::boxWidth) +, _informative(false) +, _text(100) +, _confirm(this, doneText.isEmpty() ? lang(lng_box_ok) : doneText, doneStyle) +, _cancel(this, cancelText.isEmpty() ? lang(lng_cancel) : cancelText, cancelStyle) { init(text); } -ConfirmBox::ConfirmBox(const QString &text, const QString &doneText, const style::BoxButton &doneStyle, bool informative) : AbstractBox(st::boxWidth), -_informative(true), -_text(100), -_confirm(this, doneText.isEmpty() ? lang(lng_box_ok) : doneText, doneStyle), -_cancel(this, QString(), st::cancelBoxButton) { +ConfirmBox::ConfirmBox(const QString &text, const QString &doneText, const style::BoxButton &doneStyle, bool informative) : AbstractBox(st::boxWidth) +, _informative(true) +, _text(100) +, _confirm(this, doneText.isEmpty() ? lang(lng_box_ok) : doneText, doneStyle) +, _cancel(this, QString(), st::cancelBoxButton) { init(text); } @@ -83,33 +83,30 @@ void ConfirmBox::mouseMoveEvent(QMouseEvent *e) { void ConfirmBox::mousePressEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateHover(); - if (textlnkOver()) { - textlnkDown(textlnkOver()); - update(); - } + ClickHandler::pressed(); return LayeredWidget::mousePressEvent(e); } void ConfirmBox::mouseReleaseEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateHover(); - if (textlnkOver() && textlnkOver() == textlnkDown()) { + if (ClickHandlerPtr activated = ClickHandler::unpressed()) { Ui::hideLayer(); - textlnkOver()->onClick(e->button()); + App::activateClickHandler(activated, e->button()); } - textlnkDown(TextLinkPtr()); } void ConfirmBox::leaveEvent(QEvent *e) { - if (_myLink) { - if (textlnkOver() == _myLink) { - textlnkOver(TextLinkPtr()); - update(); - } - _myLink = TextLinkPtr(); - setCursor(style::cur_default); - update(); - } + ClickHandler::clearActive(this); +} + +void ConfirmBox::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + setCursor(active ? style::cur_pointer : style::cur_default); + update(); +} + +void ConfirmBox::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { + update(); } void ConfirmBox::updateLink() { @@ -119,17 +116,12 @@ void ConfirmBox::updateLink() { void ConfirmBox::updateHover() { QPoint m(mapFromGlobal(_lastMousePos)); - bool wasMy = (_myLink == textlnkOver()); + textstyleSet(&st::boxTextStyle); - _myLink = _text.linkLeft(m.x() - st::boxPadding.left(), m.y() - st::boxPadding.top(), _textWidth, width(), (_text.maxWidth() < width()) ? style::al_center : style::al_left); + ClickHandlerPtr handler = _text.linkLeft(m.x() - st::boxPadding.left(), m.y() - st::boxPadding.top(), _textWidth, width(), style::al_left); textstyleRestore(); - if (_myLink != textlnkOver()) { - if (wasMy || _myLink || rect().contains(m)) { - textlnkOver(_myLink); - } - setCursor(_myLink ? style::cur_pointer : style::cur_default); - update(); - } + + ClickHandler::setActive(handler, this); } void ConfirmBox::closePressed() { @@ -174,22 +166,29 @@ void ConfirmBox::resizeEvent(QResizeEvent *e) { _cancel.moveToRight(st::boxButtonPadding.right() + _confirm.width() + st::boxButtonPadding.left(), _confirm.y()); } -ConfirmLinkBox::ConfirmLinkBox(const QString &url) : ConfirmBox(lang(lng_open_this_link) + qsl("\n\n") + url, lang(lng_open_link)), _url(url) { +SharePhoneConfirmBox::SharePhoneConfirmBox(PeerData *recipient) +: ConfirmBox(lang(lng_bot_share_phone), lang(lng_bot_share_phone_confirm)) +, _recipient(recipient) { + connect(this, SIGNAL(confirmed()), this, SLOT(onConfirm())); +} + +void SharePhoneConfirmBox::onConfirm() { + emit confirmed(_recipient); +} + +ConfirmLinkBox::ConfirmLinkBox(const QString &url) : ConfirmBox(lang(lng_open_this_link) + qsl("\n\n") + url, lang(lng_open_link)) +, _url(url) { connect(this, SIGNAL(confirmed()), this, SLOT(onOpenLink())); } void ConfirmLinkBox::onOpenLink() { - if (reMailStart().match(_url).hasMatch()) { - EmailLink(_url).onClick(Qt::LeftButton); - } else { - TextLink(_url).onClick(Qt::LeftButton); - } Ui::hideLayer(); + UrlClickHandler::doOpen(_url); } MaxInviteBox::MaxInviteBox(const QString &link) : AbstractBox(st::boxWidth) , _close(this, lang(lng_box_ok), st::defaultBoxButton) -, _text(st::boxTextFont, lng_participant_invite_sorry(lt_count, cMaxGroupCount()), _confirmBoxTextOptions, st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) +, _text(st::boxTextFont, lng_participant_invite_sorry(lt_count, Global::ChatSizeMax()), _confirmBoxTextOptions, st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) , _link(link) , _linkOver(false) , a_goodOpacity(0, 0) @@ -212,7 +211,7 @@ void MaxInviteBox::mouseMoveEvent(QMouseEvent *e) { void MaxInviteBox::mousePressEvent(QMouseEvent *e) { mouseMoveEvent(e); if (_linkOver) { - App::app()->clipboard()->setText(_link); + Application::clipboard()->setText(_link); _goodTextLink = lang(lng_create_channel_link_copied); a_goodOpacity = anim::fvalue(1, 0); _a_good.start(); @@ -279,3 +278,228 @@ void MaxInviteBox::resizeEvent(QResizeEvent *e) { _close.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _close.height()); _invitationLink = myrtlrect(st::boxPadding.left(), st::boxPadding.top() + _textHeight + st::boxTextFont->height, width() - st::boxPadding.left() - st::boxPadding.right(), 2 * st::boxTextFont->height); } + +ConvertToSupergroupBox::ConvertToSupergroupBox(ChatData *chat) : AbstractBox(st::boxWideWidth) +, _chat(chat) +, _text(100) +, _note(100) +, _convert(this, lang(lng_profile_convert_confirm), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { + QStringList text; + text.push_back(lang(lng_profile_convert_feature1)); + text.push_back(lang(lng_profile_convert_feature2)); + text.push_back(lang(lng_profile_convert_feature3)); + text.push_back(lang(lng_profile_convert_feature4)); + + textstyleSet(&st::boxTextStyle); + _text.setText(st::boxTextFont, text.join('\n'), _confirmBoxTextOptions); + _note.setText(st::boxTextFont, lng_profile_convert_warning(lt_bold_start, textcmdStartSemibold(), lt_bold_end, textcmdStopSemibold()), _confirmBoxTextOptions); + _textWidth = st::boxWideWidth - st::boxPadding.left() - st::boxButtonPadding.right(); + _textHeight = _text.countHeight(_textWidth); + setMaxHeight(st::boxTitleHeight + _textHeight + st::boxPadding.bottom() + _note.countHeight(_textWidth) + st::boxButtonPadding.top() + _convert.height() + st::boxButtonPadding.bottom()); + textstyleRestore(); + + connect(&_convert, SIGNAL(clicked()), this, SLOT(onConvert())); + connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + + prepare(); +} + +void ConvertToSupergroupBox::onConvert() { + MTP::send(MTPmessages_MigrateChat(_chat->inputChat), rpcDone(&ConvertToSupergroupBox::convertDone), rpcFail(&ConvertToSupergroupBox::convertFail)); +} + +void ConvertToSupergroupBox::convertDone(const MTPUpdates &updates) { + Ui::hideLayer(); + App::main()->sentUpdatesReceived(updates); + const QVector *v = 0; + switch (updates.type()) { + case mtpc_updates: v = &updates.c_updates().vchats.c_vector().v; break; + case mtpc_updatesCombined: v = &updates.c_updatesCombined().vchats.c_vector().v; break; + default: LOG(("API Error: unexpected update cons %1 (ConvertToSupergroupBox::convertDone)").arg(updates.type())); break; + } + + PeerData *peer = 0; + if (v && !v->isEmpty()) { + for (int32 i = 0, l = v->size(); i < l; ++i) { + if (v->at(i).type() == mtpc_channel) { + peer = App::channel(v->at(i).c_channel().vid.v); + Ui::showPeerHistory(peer, ShowAtUnreadMsgId); + QTimer::singleShot(ReloadChannelMembersTimeout, App::api(), SLOT(delayedRequestParticipantsCount())); + } + } + } + if (!peer) { + LOG(("API Error: channel not found in updates (ProfileInner::migrateDone)")); + } +} + +bool ConvertToSupergroupBox::convertFail(const RPCError &error) { + if (mtpIsFlood(error)) return false; + Ui::hideLayer(); + return true; +} + +void ConvertToSupergroupBox::hideAll() { + _convert.hide(); + _cancel.hide(); +} + +void ConvertToSupergroupBox::showAll() { + _convert.show(); + _cancel.show(); +} + +void ConvertToSupergroupBox::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { + onConvert(); + } else { + AbstractBox::keyPressEvent(e); + } +} + +void ConvertToSupergroupBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + paintTitle(p, lang(lng_profile_convert_title)); + + // draw box title / text + p.setPen(st::black); + textstyleSet(&st::boxTextStyle); + _text.drawLeft(p, st::boxPadding.left(), st::boxTitleHeight, _textWidth, width()); + _note.drawLeft(p, st::boxPadding.left(), st::boxTitleHeight + _textHeight + st::boxPadding.bottom(), _textWidth, width()); + textstyleRestore(); +} + +void ConvertToSupergroupBox::resizeEvent(QResizeEvent *e) { + _convert.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _convert.height()); + _cancel.moveToRight(st::boxButtonPadding.right() + _convert.width() + st::boxButtonPadding.left(), _convert.y()); +} + +PinMessageBox::PinMessageBox(ChannelData *channel, MsgId msgId) : AbstractBox(st::boxWidth) +, _channel(channel) +, _msgId(msgId) +, _text(this, lang(lng_pinned_pin_sure), st::boxLabel) +, _notify(this, lang(lng_pinned_notify), true) +, _pin(this, lang(lng_pinned_pin), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _requestId(0) { + _text.resizeToWidth(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()); + setMaxHeight(st::boxPadding.top() + _text.height() + st::boxMediumSkip + _notify.height() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _pin.height() + st::boxButtonPadding.bottom()); + + connect(&_pin, SIGNAL(clicked()), this, SLOT(onPin())); + connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); +} + +void PinMessageBox::resizeEvent(QResizeEvent *e) { + _text.moveToLeft(st::boxPadding.left(), st::boxPadding.top()); + _notify.moveToLeft(st::boxPadding.left(), _text.y() + _text.height() + st::boxMediumSkip); + _pin.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _pin.height()); + _cancel.moveToRight(st::boxButtonPadding.right() + _pin.width() + st::boxButtonPadding.left(), _pin.y()); +} + +void PinMessageBox::onPin() { + if (_requestId) return; + + MTPchannels_UpdatePinnedMessage::Flags flags = 0; + if (_notify.checked()) { + flags |= MTPchannels_UpdatePinnedMessage::Flag::f_silent; + } + _requestId = MTP::send(MTPchannels_UpdatePinnedMessage(MTP_flags(flags), _channel->inputChannel, MTP_int(_msgId)), rpcDone(&PinMessageBox::pinDone), rpcFail(&PinMessageBox::pinFail)); +} + +void PinMessageBox::showAll() { + _text.show(); + _notify.show(); + _pin.show(); + _cancel.show(); +} + +void PinMessageBox::hideAll() { + _text.hide(); + _notify.hide(); + _pin.hide(); + _cancel.hide(); +} + +void PinMessageBox::pinDone(const MTPUpdates &updates) { + if (App::main()) { + App::main()->sentUpdatesReceived(updates); + } + Ui::hideLayer(); +} + +bool PinMessageBox::pinFail(const RPCError &error) { + if (mtpIsFlood(error)) return false; + Ui::hideLayer(); + return true; +} + +RichDeleteMessageBox::RichDeleteMessageBox(ChannelData *channel, UserData *from, MsgId msgId) : AbstractBox(st::boxWidth) +, _channel(channel) +, _from(from) +, _msgId(msgId) +, _text(this, lang(lng_selected_delete_sure_this), st::boxLabel) +, _banUser(this, lang(lng_ban_user), false) +, _reportSpam(this, lang(lng_report_spam), false) +, _deleteAll(this, lang(lng_delete_all_from), false) +, _delete(this, lang(lng_box_delete), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { + t_assert(_channel != nullptr); + + _text.resizeToWidth(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()); + setMaxHeight(st::boxPadding.top() + _text.height() + st::boxMediumSkip + _banUser.height() + st::boxLittleSkip + _reportSpam.height() + st::boxLittleSkip + _deleteAll.height() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _delete.height() + st::boxButtonPadding.bottom()); + + connect(&_delete, SIGNAL(clicked()), this, SLOT(onDelete())); + connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); +} + +void RichDeleteMessageBox::resizeEvent(QResizeEvent *e) { + _text.moveToLeft(st::boxPadding.left(), st::boxPadding.top()); + _banUser.moveToLeft(st::boxPadding.left(), _text.y() + _text.height() + st::boxMediumSkip); + _reportSpam.moveToLeft(st::boxPadding.left(), _banUser.y() + _banUser.height() + st::boxLittleSkip); + _deleteAll.moveToLeft(st::boxPadding.left(), _reportSpam.y() + _reportSpam.height() + st::boxLittleSkip); + _delete.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _delete.height()); + _cancel.moveToRight(st::boxButtonPadding.right() + _delete.width() + st::boxButtonPadding.left(), _delete.y()); +} + +void RichDeleteMessageBox::onDelete() { + if (_banUser.checked()) { + MTP::send(MTPchannels_KickFromChannel(_channel->inputChannel, _from->inputUser, MTP_boolTrue()), App::main()->rpcDone(&MainWidget::sentUpdatesReceived)); + } + if (_reportSpam.checked()) { + MTP::send(MTPchannels_ReportSpam(_channel->inputChannel, _from->inputUser, MTP_vector(1, MTP_int(_msgId)))); + } + if (_deleteAll.checked()) { + App::main()->deleteAllFromUser(_channel, _from); + } + if (HistoryItem *item = App::histItemById(_channel ? peerToChannel(_channel->id) : 0, _msgId)) { + bool wasLast = (item->history()->lastMsg == item); + item->destroy(); + if (_msgId > 0) { + App::main()->deleteMessages(_channel, QVector(1, MTP_int(_msgId))); + } else if (wasLast) { + App::main()->checkPeerHistory(_channel); + } + } + Ui::hideLayer(); +} + +void RichDeleteMessageBox::showAll() { + _text.show(); + _banUser.show(); + _reportSpam.show(); + _deleteAll.show(); + _delete.show(); + _cancel.show(); +} + +void RichDeleteMessageBox::hideAll() { + _text.hide(); + _banUser.hide(); + _reportSpam.hide(); + _deleteAll.hide(); + _delete.hide(); + _cancel.hide(); +} diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index ba439eb957..097e6f4367 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -16,14 +16,14 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once #include "abstractbox.h" class InformBox; -class ConfirmBox : public AbstractBox { +class ConfirmBox : public AbstractBox, public ClickHandlerHost { Q_OBJECT public: @@ -38,6 +38,10 @@ public: void leaveEvent(QEvent *e); void updateLink(); + // ClickHandlerHost interface + void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active); + void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed); + public slots: void onCancel(); @@ -69,7 +73,6 @@ private: void updateHover(); QPoint _lastMousePos; - TextLinkPtr _myLink; BoxButton _confirm, _cancel; }; @@ -80,6 +83,23 @@ public: } }; +class SharePhoneConfirmBox : public ConfirmBox { + Q_OBJECT + +public: + SharePhoneConfirmBox(PeerData *recipient); + +signals: + void confirmed(PeerData *recipient); + +private slots: + void onConfirm(); + +private: + PeerData *_recipient; + +}; + class ConfirmLinkBox : public ConfirmBox { Q_OBJECT @@ -108,7 +128,7 @@ public: void mouseMoveEvent(QMouseEvent *e); void mousePressEvent(QMouseEvent *e); void leaveEvent(QEvent *e); - + protected: void hideAll(); @@ -133,3 +153,100 @@ private: anim::fvalue a_goodOpacity; Animation _a_good; }; + +class ConvertToSupergroupBox : public AbstractBox, public RPCSender { + Q_OBJECT + +public: + + ConvertToSupergroupBox(ChatData *chat); + void keyPressEvent(QKeyEvent *e); + void paintEvent(QPaintEvent *e); + void resizeEvent(QResizeEvent *e); + +public slots: + + void onConvert(); + +protected: + + void hideAll(); + void showAll(); + +private: + + void convertDone(const MTPUpdates &updates); + bool convertFail(const RPCError &error); + + ChatData *_chat; + Text _text, _note; + int32 _textWidth, _textHeight; + + BoxButton _convert, _cancel; +}; + +class PinMessageBox : public AbstractBox, public RPCSender { + Q_OBJECT + +public: + + PinMessageBox(ChannelData *channel, MsgId msgId); + + void resizeEvent(QResizeEvent *e); + +public slots: + + void onPin(); + +protected: + + void showAll(); + void hideAll(); + +private: + + void pinDone(const MTPUpdates &updates); + bool pinFail(const RPCError &error); + + ChannelData *_channel; + MsgId _msgId; + + FlatLabel _text; + Checkbox _notify; + + BoxButton _pin, _cancel; + + mtpRequestId _requestId; + +}; + +class RichDeleteMessageBox : public AbstractBox, public RPCSender { + Q_OBJECT + +public: + + RichDeleteMessageBox(ChannelData *channel, UserData *from, MsgId msgId); + + void resizeEvent(QResizeEvent *e); + +public slots: + + void onDelete(); + +protected: + + void showAll(); + void hideAll(); + +private: + + ChannelData *_channel; + UserData *_from; + MsgId _msgId; + + FlatLabel _text; + Checkbox _banUser, _reportSpam, _deleteAll; + + BoxButton _delete, _cancel; + +}; diff --git a/Telegram/SourceFiles/boxes/connectionbox.cpp b/Telegram/SourceFiles/boxes/connectionbox.cpp index f2c24a9e38..aa5ebd7675 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.cpp +++ b/Telegram/SourceFiles/boxes/connectionbox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" @@ -201,8 +201,10 @@ void ConnectionBox::onSave() { } else { cSetConnectionType(dbictAuto); cSetConnectionProxy(ConnectionProxy()); +#ifndef TDESKTOP_DISABLE_NETWORK_PROXY QNetworkProxyFactory::setUseSystemConfiguration(false); QNetworkProxyFactory::setUseSystemConfiguration(true); +#endif } if (cPlatform() == dbipWindows && cTryIPv6() != _tryIPv6.checked()) { cSetTryIPv6(_tryIPv6.checked()); @@ -313,9 +315,11 @@ void AutoDownloadBox::onSave() { bool enabledGroups = ((cAutoDownloadAudio() & dbiadNoGroups) && !(autoDownloadAudio & dbiadNoGroups)); cSetAutoDownloadAudio(autoDownloadAudio); if (enabledPrivate || enabledGroups) { - const AudiosData &data(App::audiosData()); - for (AudiosData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) { - i.value()->automaticLoadSettingsChanged(); + const DocumentsData &data(App::documentsData()); + for (DocumentsData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) { + if (i.value()->voice()) { + i.value()->automaticLoadSettingsChanged(); + } } } changed = true; @@ -328,7 +332,9 @@ void AutoDownloadBox::onSave() { if (enabledPrivate || enabledGroups) { const DocumentsData &data(App::documentsData()); for (DocumentsData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) { - i.value()->automaticLoadSettingsChanged(); + if (i.value()->isAnimation()) { + i.value()->automaticLoadSettingsChanged(); + } } Notify::automaticLoadSettingsChangedGif(); } diff --git a/Telegram/SourceFiles/boxes/connectionbox.h b/Telegram/SourceFiles/boxes/connectionbox.h index 79a43f378f..34a2770be7 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.h +++ b/Telegram/SourceFiles/boxes/connectionbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index 4ac079ff9f..ac8fc41d79 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" @@ -33,6 +33,10 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "confirmbox.h" +QString cantInviteError() { + return lng_cant_invite_not_contact(lt_more_info, textcmdLink(qsl("https://telegram.me/spambot"), lang(lng_cant_more_info))); +} + ContactsInner::ContactsInner(CreatingGroupType creating) : TWidget() , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _newItemHeight(creating == CreatingGroupNone ? st::contactsNewItemHeight : 0) @@ -224,7 +228,7 @@ void ContactsInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &old void ContactsInner::onAddBot() { if (_bot->botInfo && !_bot->botInfo->startGroupToken.isEmpty()) { - MTP::send(MTPmessages_StartBot(_bot->inputUser, _addToPeer->input, MTP_long(MTP::nonce()), MTP_string(_bot->botInfo->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, _bot)); + MTP::send(MTPmessages_StartBot(_bot->inputUser, _addToPeer->input, MTP_long(rand_value()), MTP_string(_bot->botInfo->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, _bot)); } else { App::main()->addParticipants(_addToPeer, QVector(1, _bot)); } @@ -257,6 +261,18 @@ void ContactsInner::addAdminDone(const MTPUpdates &result, mtpRequestId req) { if (req != _addAdminRequestId) return; _addAdminRequestId = 0; + if (_addAdmin && _channel && _channel->isMegagroup()) { + if (_channel->mgInfo->lastParticipants.indexOf(_addAdmin) < 0) { + _channel->mgInfo->lastParticipants.push_front(_addAdmin); + } + _channel->mgInfo->lastAdmins.insert(_addAdmin); + if (_addAdmin->botInfo) { + _channel->mgInfo->bots.insert(_addAdmin); + if (_channel->mgInfo->botStatus != 0 && _channel->mgInfo->botStatus < 2) { + _channel->mgInfo->botStatus = 2; + } + } + } if (_addAdminBox) _addAdminBox->onClose(); emit adminAdded(); } @@ -272,6 +288,8 @@ bool ContactsInner::addAdminFail(const RPCError &error, mtpRequestId req) { Ui::showLayer(new MaxInviteBox(_channel->invitationUrl), KeepOtherLayers); } else if (error.type() == "ADMINS_TOO_MUCH") { Ui::showLayer(new InformBox(lang(lng_channel_admins_too_much)), KeepOtherLayers); + } else if (error.type() == qstr("USER_RESTRICTED")) { + Ui::showLayer(new InformBox(lang(lng_cant_do_this)), KeepOtherLayers); } else { emit adminAdded(); } @@ -350,7 +368,7 @@ void ContactsInner::loadProfilePhotos(int32 yFrom) { preloadFrom != _contacts->list.end && (_newItemHeight + preloadFrom->pos * _rowHeight) < yTo; preloadFrom = preloadFrom->next ) { - preloadFrom->history->peer->photo->load(); + preloadFrom->history->peer->loadUserpic(); } } } else if (!_filtered.isEmpty()) { @@ -361,7 +379,7 @@ void ContactsInner::loadProfilePhotos(int32 yFrom) { if (to > _filtered.size()) to = _filtered.size(); for (; from < to; ++from) { - _filtered[from]->history->peer->photo->load(); + _filtered[from]->history->peer->loadUserpic(); } } } @@ -427,13 +445,13 @@ void ContactsInner::paintDialog(Painter &p, PeerData *peer, ContactData *data, b sel = false; } } else { - if (data->inchat || data->check || selectedCount() >= ((_channel && _channel->isMegagroup()) ? cMaxMegaGroupCount() : cMaxGroupCount())) { + if (data->inchat || data->check || selectedCount() >= Global::MegagroupSizeMax()) { sel = false; } } p.fillRect(0, 0, width(), _rowHeight, inverse ? st::contactsBgActive : (sel ? st::contactsBgOver : st::white)); p.setPen(inverse ? st::white : st::black); - p.drawPixmapLeft(st::contactsPadding.left(), st::contactsPadding.top(), width(), peer->photo->pix(st::contactsPhotoSize)); + peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width()); int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); int32 iconw = (_chat || _creating != CreatingGroupNone) ? (st::contactsCheckPosition.x() * 2 + st::contactsCheckIcon.pxWidth()) : 0; @@ -767,10 +785,14 @@ void ContactsInner::changeCheckState(ContactData *data, PeerData *peer) { data->check = false; _checkedContacts.remove(peer); --_selCount; - } else if (selectedCount() < ((_channel && _channel->isMegagroup()) ? cMaxMegaGroupCount() : cMaxGroupCount())) { + } else if (selectedCount() < ((_channel && _channel->isMegagroup()) ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) { data->check = true; _checkedContacts.insert(peer, true); ++_selCount; + } else if (_channel && !_channel->isMegagroup()) { + Ui::showLayer(new MaxInviteBox(_channel->invitationUrl), KeepOtherLayers); + } else if (!_channel && selectedCount() >= Global::ChatSizeMax() && selectedCount() < Global::MegagroupSizeMax()) { + Ui::showLayer(new InformBox(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), KeepOtherLayers); } if (cnt != _selCount) emit chosenChanged(); } @@ -1535,7 +1557,7 @@ void ContactsBox::paintEvent(QPaintEvent *e) { paintTitle(p, lang(lng_channel_admins)); } else if (_inner.chat() || _inner.creating() != CreatingGroupNone) { QString title(lang(addingAdmin ? lng_channel_add_admin : lng_profile_add_participant)); - QString additional(addingAdmin ? QString() : QString("%1 / %2").arg(_inner.selectedCount()).arg(((_inner.channel() && _inner.channel()->isMegagroup()) ? cMaxMegaGroupCount() : cMaxGroupCount()))); + QString additional((addingAdmin || (_inner.channel() && !_inner.channel()->isMegagroup())) ? QString() : QString("%1 / %2").arg(_inner.selectedCount()).arg(Global::MegagroupSizeMax())); paintTitle(p, title, additional); } else if (_inner.bot()) { paintTitle(p, lang(lng_bot_choose_group)); @@ -1652,12 +1674,15 @@ void ContactsBox::getAdminsDone(const MTPmessages_ChatFull &result) { } } _saveRequestId = 0; - for (ChatData::Admins::const_iterator i = curadmins.cbegin(), e = curadmins.cend(); i != e; ++i) { - MTP::send(MTPmessages_EditChatAdmin(_inner.chat()->inputChat, i.key()->inputUser, MTP_boolFalse()), rpcDone(&ContactsBox::removeAdminDone, i.key()), rpcFail(&ContactsBox::editAdminFail), 0, (appoint.isEmpty() && i + 1 == e) ? 0 : 10); + + for_const (UserData *user, curadmins) { + MTP::send(MTPmessages_EditChatAdmin(_inner.chat()->inputChat, user->inputUser, MTP_boolFalse()), rpcDone(&ContactsBox::removeAdminDone, user), rpcFail(&ContactsBox::editAdminFail), 0, 10); } - for (int32 i = 0, l = appoint.size(); i < l; ++i) { - MTP::send(MTPmessages_EditChatAdmin(_inner.chat()->inputChat, appoint.at(i)->inputUser, MTP_boolTrue()), rpcDone(&ContactsBox::setAdminDone, appoint.at(i)), rpcFail(&ContactsBox::editAdminFail), 0, (i + 1 == l) ? 0 : 10); + for_const (UserData *user, appoint) { + MTP::send(MTPmessages_EditChatAdmin(_inner.chat()->inputChat, user->inputUser, MTP_boolTrue()), rpcDone(&ContactsBox::setAdminDone, user), rpcFail(&ContactsBox::editAdminFail), 0, 10); } + MTP::sendAnything(); + _saveRequestId = curadmins.size() + appoint.size(); if (!_saveRequestId) { onClose(); @@ -1669,7 +1694,7 @@ void ContactsBox::setAdminDone(UserData *user, const MTPBool &result) { if (_inner.chat()->noParticipantInfo()) { App::api()->requestFullPeer(_inner.chat()); } else { - _inner.chat()->admins.insert(user, true); + _inner.chat()->admins.insert(user); } } --_saveRequestId; @@ -1704,7 +1729,13 @@ bool ContactsBox::editAdminFail(const RPCError &error) { if (mtpIsFlood(error)) return true; --_saveRequestId; _inner.chat()->invalidateParticipants(); - if (!_saveRequestId) onClose(); + if (!_saveRequestId) { + if (error.type() == qstr("USER_RESTRICTED")) { + Ui::showLayer(new InformBox(lang(lng_cant_do_this))); + return true; + } + onClose(); + } return false; } @@ -1749,7 +1780,10 @@ bool ContactsBox::creationFail(const RPCError &error) { _filter.showError(); return true; } else if (error.type() == "PEER_FLOOD") { - Ui::showLayer(new InformBox(lng_cant_invite_not_contact(lt_more_info, textcmdLink(qsl("https://telegram.org/faq?_hash=can-39t-send-messages-to-non-contacts"), lang(lng_cant_more_info)))), KeepOtherLayers); + Ui::showLayer(new InformBox(cantInviteError()), KeepOtherLayers); + return true; + } else if (error.type() == qstr("USER_RESTRICTED")) { + Ui::showLayer(new InformBox(lang(lng_cant_do_this))); return true; } return false; @@ -1757,7 +1791,7 @@ bool ContactsBox::creationFail(const RPCError &error) { MembersInner::MembersInner(ChannelData *channel, MembersFilter filter) : TWidget() , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) -, _newItemHeight((channel->amCreator() && (channel->count < (channel->isMegagroup() ? cMaxMegaGroupCount() : cMaxGroupCount()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilterAdmins)) ? st::contactsNewItemHeight : 0) +, _newItemHeight((channel->amCreator() && (channel->count < (channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilterAdmins)) ? st::contactsNewItemHeight : 0) , _newItemSel(false) , _channel(channel) , _filter(filter) @@ -1787,7 +1821,7 @@ MembersInner::MembersInner(ChannelData *channel, MembersFilter filter) : TWidget void MembersInner::load() { if (!_loadingRequestId) { - _loadingRequestId = MTP::send(MTPchannels_GetParticipants(_channel->inputChannel, (_filter == MembersFilterRecent) ? MTP_channelParticipantsRecent() : MTP_channelParticipantsAdmins(), MTP_int(0), MTP_int(cMaxGroupCount())), rpcDone(&MembersInner::membersReceived), rpcFail(&MembersInner::membersFailed)); + _loadingRequestId = MTP::send(MTPchannels_GetParticipants(_channel->inputChannel, (_filter == MembersFilterRecent) ? MTP_channelParticipantsRecent() : MTP_channelParticipantsAdmins(), MTP_int(0), MTP_int(Global::ChatSizeMax())), rpcDone(&MembersInner::membersReceived), rpcFail(&MembersInner::membersFailed)); } } @@ -1826,7 +1860,7 @@ void MembersInner::paintEvent(QPaintEvent *e) { paintDialog(p, _rows[from], data(from), sel, kickSel, kickDown); p.translate(0, _rowHeight); } - if (to == _rows.size() && _filter == MembersFilterRecent && (_rows.size() < _channel->count || _rows.size() >= cMaxGroupCount())) { + if (to == _rows.size() && _filter == MembersFilterRecent && (_rows.size() < _channel->count || _rows.size() >= Global::ChatSizeMax())) { p.setPen(st::stickersReorderFg); _about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); } @@ -1895,7 +1929,7 @@ void MembersInner::paintDialog(Painter &p, PeerData *peer, MemberData *data, boo UserData *user = peer->asUser(); p.fillRect(0, 0, width(), _rowHeight, (sel ? st::contactsBgOver : st::white)->b); - p.drawPixmapLeft(st::contactsPadding.left(), st::contactsPadding.top(), width(), peer->photo->pix(st::contactsPhotoSize)); + peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width()); p.setPen(st::black); @@ -1980,7 +2014,7 @@ void MembersInner::loadProfilePhotos(int32 yFrom) { if (to > _rows.size()) to = _rows.size(); for (; from < to; ++from) { - _rows[from]->photo->load(); + _rows[from]->loadUserpic(); } } } @@ -2005,7 +2039,7 @@ void MembersInner::refresh() { } else { _about.setText(st::boxTextFont, lng_channel_only_last_shown(lt_count, _rows.size())); _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); - if (_filter != MembersFilterRecent || (_rows.size() >= _channel->count && _rows.size() < cMaxGroupCount())) { + if (_filter != MembersFilterRecent || (_rows.size() >= _channel->count && _rows.size() < Global::ChatSizeMax())) { _aboutHeight = 0; } resize(width(), st::membersPadding.top() + _newItemHeight + _rows.size() * _rowHeight + st::membersPadding.bottom() + _aboutHeight); @@ -2182,6 +2216,16 @@ void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result _datas.push_back(0); } } + + // update admins if we got all of them + if (_filter == MembersFilterAdmins && _channel->isMegagroup() && _rows.size() < Global::ChatSizeMax()) { + _channel->mgInfo->lastAdmins.clear(); + for (int32 i = 0, l = _rows.size(); i != l; ++i) { + if (_roles.at(i) == MemberRoleCreator || _roles.at(i) == MemberRoleEditor) { + _channel->mgInfo->lastAdmins.insert(_rows.at(i)); + } + } + } } if (_rows.isEmpty()) { _rows.push_back(App::self()); @@ -2220,7 +2264,7 @@ void MembersInner::kickAdminDone(const MTPUpdates &result, mtpRequestId req) { bool MembersInner::kickFail(const RPCError &error, mtpRequestId req) { if (mtpIsFlood(error)) return false; - + if (_kickBox) _kickBox->onClose(); load(); return true; @@ -2297,7 +2341,7 @@ void MembersBox::onScroll() { } void MembersBox::onAdd() { - if (_inner.filter() == MembersFilterRecent && _inner.channel()->count >= (_inner.channel()->isMegagroup() ? cMaxMegaGroupCount() : cMaxGroupCount())) { + if (_inner.filter() == MembersFilterRecent && _inner.channel()->count >= (_inner.channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) { Ui::showLayer(new MaxInviteBox(_inner.channel()->invitationUrl), KeepOtherLayers); return; } diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h index 540f35b40c..87d7ad8072 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.h +++ b/Telegram/SourceFiles/boxes/contactsbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -28,6 +28,8 @@ enum MembersFilter { }; typedef QMap MembersAlreadyIn; +QString cantInviteError(); + class ConfirmBox; class ContactsInner : public TWidget, public RPCSender { Q_OBJECT @@ -51,7 +53,7 @@ public: void mouseMoveEvent(QMouseEvent *e); void mousePressEvent(QMouseEvent *e); void resizeEvent(QResizeEvent *e); - + void paintDialog(Painter &p, PeerData *peer, ContactData *data, bool sel); void updateFilter(QString filter = QString()); @@ -136,7 +138,7 @@ private: UserData *_addAdmin; mtpRequestId _addAdminRequestId; ConfirmBox *_addAdminBox; - + int32 _time; DialogsIndexed *_contacts; diff --git a/Telegram/SourceFiles/boxes/downloadpathbox.cpp b/Telegram/SourceFiles/boxes/downloadpathbox.cpp index b5d7f5d589..00c7a71588 100644 --- a/Telegram/SourceFiles/boxes/downloadpathbox.cpp +++ b/Telegram/SourceFiles/boxes/downloadpathbox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" diff --git a/Telegram/SourceFiles/boxes/downloadpathbox.h b/Telegram/SourceFiles/boxes/downloadpathbox.h index c4a335e304..8f6e923b83 100644 --- a/Telegram/SourceFiles/boxes/downloadpathbox.h +++ b/Telegram/SourceFiles/boxes/downloadpathbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/boxes/emojibox.cpp b/Telegram/SourceFiles/boxes/emojibox.cpp index a5b4837475..9bd637bed3 100644 --- a/Telegram/SourceFiles/boxes/emojibox.cpp +++ b/Telegram/SourceFiles/boxes/emojibox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" diff --git a/Telegram/SourceFiles/boxes/emojibox.h b/Telegram/SourceFiles/boxes/emojibox.h index 405fd127b0..8fb3baac76 100644 --- a/Telegram/SourceFiles/boxes/emojibox.h +++ b/Telegram/SourceFiles/boxes/emojibox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/boxes/languagebox.cpp b/Telegram/SourceFiles/boxes/languagebox.cpp index 3ec7ea7eac..9400077e6c 100644 --- a/Telegram/SourceFiles/boxes/languagebox.cpp +++ b/Telegram/SourceFiles/boxes/languagebox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" @@ -46,12 +46,12 @@ _close(this, lang(lng_box_ok), st::defaultBoxButton) { for (int32 i = 0; i < languageCount; ++i) { LangLoaderResult result; if (i) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[i] + qsl(".strings"), LangLoaderRequest(lng_language_name)); + LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), LangLoaderRequest(lng_language_name)); result = loader.found(); } else { result.insert(lng_language_name, langOriginal(lng_language_name)); } - _langs.push_back(new Radiobutton(this, qsl("lang"), i, result.value(lng_language_name, LanguageCodes[i] + qsl(" language")), (cLang() == i), st::langsButton)); + _langs.push_back(new Radiobutton(this, qsl("lang"), i, result.value(lng_language_name, LanguageCodes[i].c_str() + qsl(" language")), (cLang() == i), st::langsButton)); _langs.back()->move(st::boxPadding.left() + st::boxOptionListPadding.left(), y); y += _langs.back()->height() + st::boxOptionListPadding.top(); connect(_langs.back(), SIGNAL(changed()), this, SLOT(onChange())); @@ -82,14 +82,14 @@ void LanguageBox::showAll() { void LanguageBox::mousePressEvent(QMouseEvent *e) { if ((e->modifiers() & Qt::CTRL) && (e->modifiers() & Qt::ALT) && (e->modifiers() & Qt::SHIFT)) { for (int32 i = 1; i < languageCount; ++i) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[i] + qsl(".strings"), LangLoaderRequest(lngkeys_cnt)); + LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), LangLoaderRequest(lngkeys_cnt)); if (!loader.errors().isEmpty()) { - Ui::showLayer(new InformBox(qsl("Lang \"") + LanguageCodes[i] + qsl("\" error :(\n\nError: ") + loader.errors())); + Ui::showLayer(new InformBox(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" error :(\n\nError: ") + loader.errors())); return; } else if (!loader.warnings().isEmpty()) { QString warn = loader.warnings(); - if (warn.size() > 256) warn = warn.mid(0, 254) + qsl(".."); - Ui::showLayer(new InformBox(qsl("Lang \"") + LanguageCodes[i] + qsl("\" warnings :(\n\nWarnings: ") + warn)); + if (warn.size() > 256) warn = warn.mid(0, 253) + qsl("..."); + Ui::showLayer(new InformBox(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" warnings :(\n\nWarnings: ") + warn)); return; } } @@ -112,7 +112,7 @@ void LanguageBox::onChange() { if (_langs[i]->checked() && langId != cLang()) { LangLoaderResult result; if (langId > 0) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[langId] + qsl(".strings"), LangLoaderRequest(lng_sure_save_language, lng_cancel, lng_box_ok)); + LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[langId].c_str() + qsl(".strings"), LangLoaderRequest(lng_sure_save_language, lng_cancel, lng_box_ok)); result = loader.found(); } else if (langId == languageTest) { LangLoaderPlain loader(cLangFile(), LangLoaderRequest(lng_sure_save_language, lng_cancel, lng_box_ok)); diff --git a/Telegram/SourceFiles/boxes/languagebox.h b/Telegram/SourceFiles/boxes/languagebox.h index ce37f417c9..68b7c65ce7 100644 --- a/Telegram/SourceFiles/boxes/languagebox.h +++ b/Telegram/SourceFiles/boxes/languagebox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/boxes/passcodebox.cpp b/Telegram/SourceFiles/boxes/passcodebox.cpp index 45055b2cf4..c3e2b985b6 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.cpp +++ b/Telegram/SourceFiles/boxes/passcodebox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" @@ -400,12 +400,12 @@ void PasscodeBox::onSave(bool force) { if (!_oldPasscode.isHidden()) { hashSha256(oldPasswordData.constData(), oldPasswordData.size(), oldPasswordHash.data()); } - int32 flags = MTPDaccount_passwordInputSettings::flag_new_salt | MTPDaccount_passwordInputSettings::flag_new_password_hash | MTPDaccount_passwordInputSettings::flag_hint; + MTPDaccount_passwordInputSettings::Flags flags = MTPDaccount_passwordInputSettings::Flag::f_new_salt | MTPDaccount_passwordInputSettings::Flag::f_new_password_hash | MTPDaccount_passwordInputSettings::Flag::f_hint; if (_oldPasscode.isHidden() || _newPasscode.isHidden()) { - flags |= MTPDaccount_passwordInputSettings::flag_email; + flags |= MTPDaccount_passwordInputSettings::Flag::f_email; } - MTPaccount_PasswordInputSettings settings(MTP_account_passwordInputSettings(MTP_int(flags), MTP_string(_newSalt), MTP_string(newPasswordHash), MTP_string(hint), MTP_string(email))); - _setRequest = MTP::send(MTPaccount_UpdatePasswordSettings(MTP_string(oldPasswordHash), settings), rpcDone(&PasscodeBox::setPasswordDone), rpcFail(&PasscodeBox::setPasswordFail)); + MTPaccount_PasswordInputSettings settings(MTP_account_passwordInputSettings(MTP_flags(flags), MTP_bytes(_newSalt), MTP_bytes(newPasswordHash), MTP_string(hint), MTP_string(email))); + _setRequest = MTP::send(MTPaccount_UpdatePasswordSettings(MTP_bytes(oldPasswordHash), settings), rpcDone(&PasscodeBox::setPasswordDone), rpcFail(&PasscodeBox::setPasswordFail)); } } else { cSetPasscodeBadTries(0); diff --git a/Telegram/SourceFiles/boxes/passcodebox.h b/Telegram/SourceFiles/boxes/passcodebox.h index f280af9a07..be908ae1a0 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.h +++ b/Telegram/SourceFiles/boxes/passcodebox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/boxes/photocropbox.cpp b/Telegram/SourceFiles/boxes/photocropbox.cpp index e76410712b..840d0dc4d6 100644 --- a/Telegram/SourceFiles/boxes/photocropbox.cpp +++ b/Telegram/SourceFiles/boxes/photocropbox.cpp @@ -16,10 +16,10 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" +#include "gui/style.h" #include "lang.h" #include "application.h" diff --git a/Telegram/SourceFiles/boxes/photocropbox.h b/Telegram/SourceFiles/boxes/photocropbox.h index 347d240647..e7c63fd025 100644 --- a/Telegram/SourceFiles/boxes/photocropbox.h +++ b/Telegram/SourceFiles/boxes/photocropbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/boxes/photosendbox.cpp b/Telegram/SourceFiles/boxes/photosendbox.cpp index d9d933e30b..adde2f0041 100644 --- a/Telegram/SourceFiles/boxes/photosendbox.cpp +++ b/Telegram/SourceFiles/boxes/photosendbox.cpp @@ -16,10 +16,10 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" +#include "gui/style.h" #include "lang.h" #include "localstorage.h" @@ -69,8 +69,8 @@ PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxW if (_animated) { int32 limitW = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); int32 limitH = st::confirmMaxHeight; - maxW = dimensions.width(); - maxH = dimensions.height(); + maxW = qMax(dimensions.width(), 1); + maxH = qMax(dimensions.height(), 1); if (maxW * limitH > maxH * limitW) { if (maxW < limitW) { maxH = maxH * limitW / maxW; @@ -82,7 +82,7 @@ PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxW maxH = limitH; } } - _thumb = imagePix(_file->thumb.toImage(), maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), true, true, false, maxW, maxH); + _thumb = imagePix(_file->thumb.toImage(), maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), ImagePixSmooth | ImagePixBlurred, maxW, maxH); } else { for (PreparedPhotoThumbs::const_iterator i = _file->photoThumbs.cbegin(), e = _file->photoThumbs.cend(); i != e; ++i) { if (i->width() >= maxW && i->height() >= maxH) { @@ -124,7 +124,7 @@ PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxW } else { _thumbw = st::msgFileThumbSize; } - _thumb = imagePix(_thumb.toImage(), _thumbw * cIntRetinaFactor(), 0, true, false, true, st::msgFileThumbSize, st::msgFileThumbSize); + _thumb = imagePix(_thumb.toImage(), _thumbw * cIntRetinaFactor(), 0, ImagePixSmooth | ImagePixRounded, st::msgFileThumbSize, st::msgFileThumbSize); } _name.setText(st::semiboldFont, _file->filename, _textNameOptions); @@ -274,7 +274,7 @@ void PhotoSendBox::paintEvent(QPaintEvent *e) { p.drawSpriteCenter(inner, _isImage ? st::msgFileOutImage : st::msgFileOutFile); } else { - p.drawPixmapLeft(x + st::msgFilePadding.left(), y + st::msgFilePadding.top(), width(), userDefPhoto(1)->pixRounded(st::msgFileSize)); + p.drawPixmapLeft(x + st::msgFilePadding.left(), y + st::msgFilePadding.top(), width(), userDefPhoto(1)->pixCircled(st::msgFileSize)); } p.setFont(st::semiboldFont); p.setPen(st::black); @@ -359,3 +359,330 @@ void PhotoSendBox::onSend(bool ctrlShiftEnter) { _confirmed = true; onClose(); } + +EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth) +, _msgId(msg->fullId()) +, _animated(false) +, _photo(false) +, _doc(false) +, _field(0) +, _save(this, lang(lng_settings_save), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _thumbx(0) +, _thumby(0) +, _thumbw(0) +, _thumbh(0) +, _statusw(0) +, _isImage(false) +, _previewCancelled(false) +, _saveRequestId(0) { + connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); + connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + + QSize dimensions; + ImagePtr image; + QString caption; + DocumentData *doc = 0; + if (HistoryMedia *media = msg->getMedia()) { + HistoryMediaType t = media->type(); + switch (t) { + case MediaTypeGif: { + _animated = true; + doc = static_cast(media)->getDocument(); + dimensions = doc->dimensions; + image = doc->thumb; + } break; + + case MediaTypePhoto: { + _photo = true; + PhotoData *photo = static_cast(media)->photo(); + dimensions = QSize(photo->full->width(), photo->full->height()); + image = photo->full; + } break; + + case MediaTypeVideo: { + _animated = true; + doc = static_cast(media)->getDocument(); + dimensions = doc->dimensions; + image = doc->thumb; + } break; + + case MediaTypeFile: + case MediaTypeMusicFile: + case MediaTypeVoiceFile: { + _doc = true; + doc = static_cast(media)->getDocument(); + image = doc->thumb; + } break; + } + caption = media->getCaption(); + } + if ((!_animated && (dimensions.isEmpty() || doc)) || image->isNull()) { + _animated = false; + if (image->isNull()) { + _thumbw = 0; + } else { + int32 tw = image->width(), th = image->height(); + if (tw > th) { + _thumbw = (tw * st::msgFileThumbSize) / th; + } else { + _thumbw = st::msgFileThumbSize; + } + _thumb = imagePix(image->pix().toImage(), _thumbw * cIntRetinaFactor(), 0, ImagePixSmooth | ImagePixRounded, st::msgFileThumbSize, st::msgFileThumbSize); + } + + if (doc) { + if (doc->voice()) { + _name.setText(st::semiboldFont, lang(lng_media_audio), _textNameOptions); + } else { + _name.setText(st::semiboldFont, documentName(doc), _textNameOptions); + } + _status = formatSizeText(doc->size); + _statusw = qMax(_name.maxWidth(), st::normalFont->width(_status)); + _isImage = doc->isImage(); + } + } else { + int32 maxW = 0, maxH = 0; + if (_animated) { + int32 limitW = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); + int32 limitH = st::confirmMaxHeight; + maxW = qMax(dimensions.width(), 1); + maxH = qMax(dimensions.height(), 1); + if (maxW * limitH > maxH * limitW) { + if (maxW < limitW) { + maxH = maxH * limitW / maxW; + maxW = limitW; + } + } else { + if (maxH < limitH) { + maxW = maxW * limitH / maxH; + maxH = limitH; + } + } + _thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), ImagePixSmooth | ImagePixBlurred, maxW, maxH); + } else { + maxW = dimensions.width(); + maxH = dimensions.height(); + _thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), ImagePixSmooth | ImagePixRounded, maxW, maxH); + } + int32 tw = _thumb.width(), th = _thumb.height(); + if (!tw || !th) { + tw = th = 1; + } + _thumbw = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); + if (_thumb.width() < _thumbw) { + _thumbw = (_thumb.width() > 20) ? _thumb.width() : 20; + } + int32 maxthumbh = qMin(qRound(1.5 * _thumbw), int(st::confirmMaxHeight)); + _thumbh = qRound(th * float64(_thumbw) / tw); + if (_thumbh > maxthumbh) { + _thumbw = qRound(_thumbw * float64(maxthumbh) / _thumbh); + _thumbh = maxthumbh; + if (_thumbw < 10) { + _thumbw = 10; + } + } + _thumbx = (width() - _thumbw) / 2; + + _thumb = QPixmap::fromImage(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); + _thumb.setDevicePixelRatio(cRetinaFactor()); + } + + if (_animated || _photo || _doc) { + _field = new InputArea(this, st::confirmCaptionArea, lang(lng_photo_caption), caption); + _field->setMaxLength(MaxPhotoCaption); + _field->setCtrlEnterSubmit(CtrlEnterSubmitBoth); + } else { + QString text = textApplyEntities(msg->originalText(), msg->originalEntities()); + _field = new InputArea(this, st::editTextArea, lang(lng_photo_caption), text); +// _field->setMaxLength(MaxMessageSize); // entities can make text in input field larger but still valid + _field->setCtrlEnterSubmit(cCtrlEnter() ? CtrlEnterSubmitCtrlEnter : CtrlEnterSubmitEnter); + } + updateBoxSize(); + connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSave(bool))); + connect(_field, SIGNAL(cancelled()), this, SLOT(onClose())); + connect(_field, SIGNAL(resized()), this, SLOT(onCaptionResized())); + + QTextCursor c(_field->textCursor()); + c.movePosition(QTextCursor::End); + _field->setTextCursor(c); + + prepare(); +} + +bool EditCaptionBox::captionFound() const { + return _animated || _photo || _doc; +} + +void EditCaptionBox::onCaptionResized() { + updateBoxSize(); + resizeEvent(0); + update(); +} + +void EditCaptionBox::updateBoxSize() { + int32 bottomh = st::boxPhotoCompressedPadding.bottom() + _field->height() + st::normalFont->height + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom(); + if (_photo || _animated) { + setMaxHeight(st::boxPhotoPadding.top() + _thumbh + bottomh); + } else if (_thumbw) { + setMaxHeight(st::boxPhotoPadding.top() + 0 + st::msgFileThumbSize + 0 + bottomh); + } else if (_doc) { + setMaxHeight(st::boxPhotoPadding.top() + 0 + st::msgFileSize + 0 + bottomh); + } else { + setMaxHeight(st::boxPhotoPadding.top() + st::boxTitleFont->height + bottomh); + } +} + +void EditCaptionBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + if (_photo || _animated) { + if (_thumbx > st::boxPhotoPadding.left()) { + p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _thumbx - st::boxPhotoPadding.left(), _thumbh, st::confirmBg->b); + } + if (_thumbx + _thumbw < width() - st::boxPhotoPadding.right()) { + p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, _thumbh, st::confirmBg->b); + } + p.drawPixmap(_thumbx, st::boxPhotoPadding.top(), _thumb); + if (_animated) { + QRect inner(_thumbx + (_thumbw - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_thumbh - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.setPen(Qt::NoPen); + p.setBrush(st::msgDateImgBg); + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.drawSpriteCenter(inner, st::msgFileInPlay); + } + } else if (_doc) { + int32 w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); + int32 h = _thumbw ? (0 + st::msgFileThumbSize + 0) : (0 + st::msgFileSize + 0); + int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0; + if (_thumbw) { + nameleft = 0 + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + nametop = st::msgFileThumbNameTop - st::msgFileThumbPadding.top(); + nameright = 0; + statustop = st::msgFileThumbStatusTop - st::msgFileThumbPadding.top(); + } else { + nameleft = 0 + st::msgFileSize + st::msgFilePadding.right(); + nametop = st::msgFileNameTop - st::msgFilePadding.top(); + nameright = 0; + statustop = st::msgFileStatusTop - st::msgFilePadding.top(); + } + int32 namewidth = w - nameleft - 0; + if (namewidth > _statusw) { + //w -= (namewidth - _statusw); + //namewidth = _statusw; + } + int32 x = (width() - w) / 2, y = st::boxPhotoPadding.top(); + +// App::roundRect(p, x, y, w, h, st::msgInBg, MessageInCorners, &st::msgInShadow); + + if (_thumbw) { + QRect rthumb(rtlrect(x + 0, y + 0, st::msgFileThumbSize, st::msgFileThumbSize, width())); + p.drawPixmap(rthumb.topLeft(), _thumb); + } else { + QRect inner(rtlrect(x + 0, y + 0, st::msgFileSize, st::msgFileSize, width())); + p.setPen(Qt::NoPen); + p.setBrush(st::msgFileInBg); + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.drawSpriteCenter(inner, _isImage ? st::msgFileInImage : st::msgFileInFile); + } + p.setFont(st::semiboldFont); + p.setPen(st::black); + _name.drawLeftElided(p, x + nameleft, y + nametop, namewidth, width()); + + style::color status(st::mediaInFg); + p.setFont(st::normalFont); + p.setPen(status); + p.drawTextLeft(x + nameleft, y + statustop, width(), _status); + } else { + p.setFont(st::boxTitleFont); + p.setPen(st::black); + p.drawTextLeft(_field->x(), st::boxPhotoPadding.top(), width(), lang(lng_edit_message)); + } + + if (!_error.isEmpty()) { + p.setFont(st::normalFont); + p.setPen(st::setErrColor); + p.drawTextLeft(_field->x(), _field->y() + _field->height() + (st::boxButtonPadding.top() / 2), width(), _error); + } +} + +void EditCaptionBox::resizeEvent(QResizeEvent *e) { + _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); + _cancel.moveToRight(st::boxButtonPadding.right() + _save.width() + st::boxButtonPadding.left(), _save.y()); + _field->resize(st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(), _field->height()); + _field->moveToLeft(st::boxPhotoPadding.left(), _save.y() - st::boxButtonPadding.top() - st::normalFont->height - _field->height()); +} + +void EditCaptionBox::hideAll() { + _save.hide(); + _cancel.hide(); + _field->hide(); +} + +void EditCaptionBox::showAll() { + _save.show(); + _cancel.show(); + _field->show(); +} + +void EditCaptionBox::showDone() { + setInnerFocus(); +} + +void EditCaptionBox::onSave(bool ctrlShiftEnter) { + if (_saveRequestId) return; + + HistoryItem *item = App::histItemById(_msgId); + if (!item) { + _error = lang(lng_edit_deleted); + update(); + return; + } + + MTPmessages_EditMessage::Flags flags = 0; + if (_previewCancelled) { + flags |= MTPmessages_EditMessage::Flag::f_no_webpage; + } + MTPVector sentEntities; + if (!sentEntities.c_vector().v.isEmpty()) { + flags |= MTPmessages_EditMessage::Flag::f_entities; + } + _saveRequestId = MTP::send(MTPmessages_EditMessage(MTP_flags(flags), item->history()->peer->input, MTP_int(item->id), MTP_string(_field->getLastText()), MTPnullMarkup, sentEntities), rpcDone(&EditCaptionBox::saveDone), rpcFail(&EditCaptionBox::saveFail)); +} + +void EditCaptionBox::saveDone(const MTPUpdates &updates) { + _saveRequestId = 0; + onClose(); + if (App::main()) { + App::main()->sentUpdatesReceived(updates); + } +} + +bool EditCaptionBox::saveFail(const RPCError &error) { + if (mtpIsFlood(error)) return false; + + _saveRequestId = 0; + QString err = error.type(); + if (err == qstr("MESSAGE_ID_INVALID") || err == qstr("CHAT_ADMIN_REQUIRED") || err == qstr("MESSAGE_EDIT_TIME_EXPIRED")) { + _error = lang(lng_edit_error); + } else if (err == qstr("MESSAGE_NOT_MODIFIED")) { + onClose(); + return true; + } else if (err == qstr("MESSAGE_EMPTY")) { + _field->setFocus(); + _field->showError(); + } else { + _error = lang(lng_edit_error); + } + update(); + return true; +} diff --git a/Telegram/SourceFiles/boxes/photosendbox.h b/Telegram/SourceFiles/boxes/photosendbox.h index 2c955dfd00..cc5801b7a7 100644 --- a/Telegram/SourceFiles/boxes/photosendbox.h +++ b/Telegram/SourceFiles/boxes/photosendbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -42,11 +42,6 @@ public: } } -signals: - - void confirmed(); - void cancelled(); - public slots: void onCompressedChange(); @@ -87,3 +82,57 @@ private: bool _confirmed; }; + +class EditCaptionBox : public AbstractBox, public RPCSender { + Q_OBJECT + +public: + + EditCaptionBox(HistoryItem *msg); + void paintEvent(QPaintEvent *e); + void resizeEvent(QResizeEvent *e); + + bool captionFound() const; + + void setInnerFocus() { + _field->setFocus(); + } + +public slots: + + void onCaptionResized(); + void onSave(bool ctrlShiftEnter = false); + +protected: + + void hideAll(); + void showAll(); + void showDone(); + +private: + + void updateBoxSize(); + + void saveDone(const MTPUpdates &updates); + bool saveFail(const RPCError &error); + + FullMsgId _msgId; + bool _animated, _photo, _doc; + + QPixmap _thumb; + + InputArea *_field; + BoxButton _save, _cancel; + + int32 _thumbx, _thumby, _thumbw, _thumbh; + Text _name; + QString _status; + int32 _statusw; + bool _isImage; + + bool _previewCancelled; + mtpRequestId _saveRequestId; + + QString _error; + +}; diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp index 6cc62071db..be56624faf 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.cpp +++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" diff --git a/Telegram/SourceFiles/boxes/sessionsbox.h b/Telegram/SourceFiles/boxes/sessionsbox.h index 3c84bc65d6..e7d60fba9b 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.h +++ b/Telegram/SourceFiles/boxes/sessionsbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index b47ee602fd..fd0d70e0ab 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" @@ -29,9 +29,17 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "localstorage.h" -StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : -_loaded(false), _setId(0), _setAccess(0), _setCount(0), _setHash(0), _setFlags(0), _bottom(0), -_input(set), _installRequest(0) { +StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : TWidget() +, _loaded(false) +, _setId(0) +, _setAccess(0) +, _setCount(0) +, _setHash(0) +, _setFlags(0) +, _bottom(0) +, _input(set) +, _installRequest(0) +, _previewShown(-1) { connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); switch (set.type()) { case mtpc_inputStickerSetID: _setId = set.c_inputStickerSetID().vid.v; _setAccess = set.c_inputStickerSetID().vaccess_hash.v; break; @@ -39,10 +47,14 @@ _input(set), _installRequest(0) { } MTP::send(MTPmessages_GetStickerSet(_input), rpcDone(&StickerSetInner::gotSet), rpcFail(&StickerSetInner::failedSet)); App::main()->updateStickers(); + + _previewTimer.setSingleShot(true); + connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); } void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { _pack.clear(); + _emoji.clear(); if (set.type() == mtpc_messages_stickerSet) { const MTPDmessages_stickerSet &d(set.c_messages_stickerSet()); const QVector &v(d.vdocuments.c_vector().v); @@ -50,9 +62,26 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { for (int32 i = 0, l = v.size(); i < l; ++i) { DocumentData *doc = App::feedDocument(v.at(i)); if (!doc || !doc->sticker()) continue; - + _pack.push_back(doc); } + const QVector &packs(d.vpacks.c_vector().v); + for (int32 i = 0, l = packs.size(); i < l; ++i) { + if (packs.at(i).type() != mtpc_stickerPack) continue; + const MTPDstickerPack &pack(packs.at(i).c_stickerPack()); + if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) { + const QVector &stickers(pack.vdocuments.c_vector().v); + StickerPack p; + p.reserve(stickers.size()); + for (int32 j = 0, c = stickers.size(); j < c; ++j) { + DocumentData *doc = App::document(stickers.at(j).v); + if (!doc || !doc->sticker()) continue; + + p.push_back(doc); + } + _emoji.insert(e, p); + } + } if (d.vset.type() == mtpc_stickerSet) { const MTPDstickerSet &s(d.vset.c_stickerSet()); _setTitle = stickerSetTitle(s); @@ -88,12 +117,17 @@ bool StickerSetInner::failedSet(const RPCError &error) { } void StickerSetInner::installDone(const MTPBool &result) { - StickerSets &sets(cRefStickerSets()); + Stickers::Sets &sets(Global::RefStickerSets()); - _setFlags &= ~MTPDstickerSet::flag_disabled; - sets.insert(_setId, StickerSet(_setId, _setAccess, _setTitle, _setShortName, _setCount, _setHash, _setFlags)).value().stickers = _pack; + _setFlags &= ~MTPDstickerSet::Flag::f_disabled; + auto it = sets.find(_setId); + if (it == sets.cend()) { + it = sets.insert(_setId, Stickers::Set(_setId, _setAccess, _setTitle, _setShortName, _setCount, _setHash, _setFlags)); + } + it.value().stickers = _pack; + it.value().emoji = _emoji; - StickerSetsOrder &order(cRefStickerSetsOrder()); + Stickers::Order &order(Global::RefStickerSetsOrder()); int32 insertAtIndex = 0, currentIndex = order.indexOf(_setId); if (currentIndex != insertAtIndex) { if (currentIndex > 0) { @@ -102,7 +136,7 @@ void StickerSetInner::installDone(const MTPBool &result) { order.insert(insertAtIndex, _setId); } - StickerSets::iterator custom = sets.find(CustomStickerSetId); + auto custom = sets.find(Stickers::CustomSetId); if (custom != sets.cend()) { for (int32 i = 0, l = _pack.size(); i < l; ++i) { int32 removeIndex = custom->stickers.indexOf(_pack.at(i)); @@ -125,6 +159,47 @@ bool StickerSetInner::installFailed(const RPCError &error) { return true; } +void StickerSetInner::mousePressEvent(QMouseEvent *e) { + int32 index = stickerFromGlobalPos(e->globalPos()); + if (index >= 0 && index < _pack.size()) { + _previewTimer.start(QApplication::startDragTime()); + } +} + +void StickerSetInner::mouseMoveEvent(QMouseEvent *e) { + if (_previewShown >= 0) { + int32 index = stickerFromGlobalPos(e->globalPos()); + if (index >= 0 && index < _pack.size() && index != _previewShown) { + _previewShown = index; + Ui::showStickerPreview(_pack.at(_previewShown)); + } + } +} + +void StickerSetInner::mouseReleaseEvent(QMouseEvent *e) { + _previewTimer.stop(); +} + +void StickerSetInner::onPreview() { + int32 index = stickerFromGlobalPos(QCursor::pos()); + if (index >= 0 && index < _pack.size()) { + _previewShown = index; + Ui::showStickerPreview(_pack.at(_previewShown)); + } +} + +int32 StickerSetInner::stickerFromGlobalPos(const QPoint &p) const { + QPoint l(mapFromGlobal(p)); + if (rtl()) l.setX(width() - l.x()); + int32 row = (l.y() >= st::stickersPadding.top()) ? qFloor((l.y() - st::stickersPadding.top()) / st::stickersSize.height()) : -1; + int32 col = (l.x() >= st::stickersPadding.left()) ? qFloor((l.x() - st::stickersPadding.left()) / st::stickersSize.width()) : -1; + if (row >= 0 && col >= 0 && col < StickerPanPerRow) { + int32 result = row * StickerPanPerRow + col; + return (result < _pack.size()) ? result : -1; + } + return -1; +} + void StickerSetInner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); @@ -149,12 +224,8 @@ void StickerSetInner::paintEvent(QPaintEvent *e) { if (doc->status == FileReady) { doc->automaticLoad(0); } - if (doc->sticker()->img->isNull() && doc->loaded() && doc->loaded(true)) { - if (doc->data().isEmpty()) { - doc->sticker()->img = ImagePtr(doc->already()); - } else { - doc->sticker()->img = ImagePtr(doc->data()); - } + if (doc->sticker()->img->isNull() && doc->loaded(DocumentData::FilePathResolveChecked)) { + doc->sticker()->img = doc->data().isEmpty() ? ImagePtr(doc->filepath()) : ImagePtr(doc->data()); } } @@ -185,8 +256,8 @@ bool StickerSetInner::loaded() const { int32 StickerSetInner::notInstalled() const { if (!_loaded) return 0; - StickerSets::const_iterator it = cStickerSets().constFind(_setId); - if (it == cStickerSets().cend() || (it->flags & MTPDstickerSet::flag_disabled)) return _pack.size(); + auto it = Global::StickerSets().constFind(_setId); + if (it == Global::StickerSets().cend() || (it->flags & MTPDstickerSet::Flag::f_disabled)) return _pack.size(); return 0; } @@ -507,7 +578,7 @@ void StickersInner::onUpdateSelected() { float64 StickersInner::aboveShadowOpacity() const { if (_above < 0) return 0; - + int32 dx = 0; int32 dy = qAbs(_above * _rowHeight + _rows.at(_above)->yadd.current() - _started * _rowHeight); return qMin((dx + dy) * 2. / _rowHeight, 1.); @@ -611,14 +682,14 @@ void StickersInner::rebuild() { int32 namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x() - qMax(qMax(_returnWidth, _removeWidth), _restoreWidth); clear(); - const StickerSetsOrder &order(cStickerSetsOrder()); + const Stickers::Order &order(Global::StickerSetsOrder()); _animStartTimes.reserve(order.size()); - - const StickerSets &sets(cStickerSets()); - for (int32 i = 0, l = order.size(); i < l; ++i) { - StickerSets::const_iterator it = sets.constFind(order.at(i)); + + const Stickers::Sets &sets(Global::StickerSets()); + for (int i = 0, l = order.size(); i < l; ++i) { + auto it = sets.constFind(order.at(i)); if (it != sets.cend()) { - bool disabled = (it->flags & MTPDstickerSet::flag_disabled); + bool disabled = (it->flags & MTPDstickerSet::Flag::f_disabled); DocumentData *sticker = it->stickers.isEmpty() ? 0 : it->stickers.at(0); int32 pixw = 0, pixh = 0; @@ -643,10 +714,10 @@ void StickersInner::rebuild() { if (titleWidth > namew) { title = st::contactsNameFont->elided(title, namew); } - bool official = (it->flags & MTPDstickerSet::flag_official); + bool official = (it->flags & MTPDstickerSet::Flag::f_official); (disabled ? rowsDisabled : rows).push_back(new StickerSetRow(it->id, sticker, it->stickers.size(), title, official, disabled, pixw, pixh)); _animStartTimes.push_back(0); - if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_flag_NOT_LOADED)) { + if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { App::api()->scheduleStickerSetRequest(it->id, it->access); } } @@ -661,8 +732,8 @@ QVector StickersInner::getOrder() const { result.reserve(_rows.size()); for (int32 i = 0, l = _rows.size(); i < l; ++i) { if (_rows.at(i)->disabled) { - StickerSets::const_iterator it = cStickerSets().constFind(_rows.at(i)->id); - if (it == cStickerSets().cend() || !(it->flags & MTPDstickerSet::flag_official)) { + auto it = Global::StickerSets().constFind(_rows.at(i)->id); + if (it == Global::StickerSets().cend() || !(it->flags & MTPDstickerSet::Flag::f_official)) { continue; } } @@ -704,6 +775,7 @@ StickersBox::StickersBox() : ItemListBox(st::boxScroll) setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); + App::main()->updateStickers(); connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); @@ -761,7 +833,7 @@ void StickersBox::reorderDone(const MTPBool &result) { bool StickersBox::reorderFail(const RPCError &result) { if (mtpIsFlood(result)) return false; _reorderRequest = 0; - cSetLastStickersUpdate(0); + Global::SetLastStickersUpdate(0); App::main()->updateStickers(); onClose(); return true; @@ -785,12 +857,12 @@ void StickersBox::closePressed() { MTP::cancel(i.key()); } _disenableRequests.clear(); - cSetLastStickersUpdate(0); + Global::SetLastStickersUpdate(0); App::main()->updateStickers(); } else if (_reorderRequest) { MTP::cancel(_reorderRequest); _reorderRequest = 0; - cSetLastStickersUpdate(0); + Global::SetLastStickersUpdate(0); App::main()->updateStickers(); } } @@ -842,11 +914,11 @@ void StickersBox::onSave() { bool writeRecent = false; RecentStickerPack &recent(cGetRecentStickers()); - StickerSets &sets(cRefStickerSets()); + Stickers::Sets &sets(Global::RefStickerSets()); QVector reorder = _inner.getOrder(), disabled = _inner.getDisabledSets(); for (int32 i = 0, l = disabled.size(); i < l; ++i) { - StickerSets::iterator it = sets.find(disabled.at(i)); + auto it = sets.find(disabled.at(i)); if (it != sets.cend()) { for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) { if (it->stickers.indexOf(i->first) >= 0) { @@ -856,35 +928,35 @@ void StickersBox::onSave() { ++i; } } - if (!(it->flags & MTPDstickerSet::flag_disabled)) { + if (!(it->flags & MTPDstickerSet::Flag::f_disabled)) { MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); - if (it->flags & MTPDstickerSet::flag_official) { + if (it->flags & MTPDstickerSet::Flag::f_official) { _disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolTrue()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType()); - it->flags |= MTPDstickerSet::flag_disabled; + it->flags |= MTPDstickerSet::Flag::f_disabled; } else { _disenableRequests.insert(MTP::send(MTPmessages_UninstallStickerSet(setId), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType()); - int32 removeIndex = cStickerSetsOrder().indexOf(it->id); - if (removeIndex >= 0) cRefStickerSetsOrder().removeAt(removeIndex); + int removeIndex = Global::StickerSetsOrder().indexOf(it->id); + if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); sets.erase(it); } } } } - StickerSetsOrder &order(cRefStickerSetsOrder()); + Stickers::Order &order(Global::RefStickerSetsOrder()); order.clear(); - for (int32 i = 0, l = reorder.size(); i < l; ++i) { - StickerSets::iterator it = sets.find(reorder.at(i)); + for (int i = 0, l = reorder.size(); i < l; ++i) { + auto it = sets.find(reorder.at(i)); if (it != sets.cend()) { - if ((it->flags & MTPDstickerSet::flag_disabled) && !disabled.contains(it->id)) { + if ((it->flags & MTPDstickerSet::Flag::f_disabled) && !disabled.contains(it->id)) { MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); _disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolFalse()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType()); - it->flags &= ~MTPDstickerSet::flag_disabled; + it->flags &= ~MTPDstickerSet::Flag::f_disabled; } order.push_back(reorder.at(i)); } } - for (StickerSets::iterator it = sets.begin(); it != sets.cend();) { - if (it->id == CustomStickerSetId || it->id == RecentStickerSetId || order.contains(it->id)) { + for (auto it = sets.begin(); it != sets.cend();) { + if (it->id == Stickers::CustomSetId || it->id == Stickers::RecentSetId || order.contains(it->id)) { ++it; } else { it = sets.erase(it); @@ -920,12 +992,12 @@ void StickersBox::showAll() { int32 stickerPacksCount(bool includeDisabledOfficial) { int32 result = 0; - const StickerSetsOrder &order(cStickerSetsOrder()); - const StickerSets &sets(cStickerSets()); - for (int32 i = 0, l = order.size(); i < l; ++i) { - StickerSets::const_iterator it = sets.constFind(order.at(i)); + const Stickers::Order &order(Global::StickerSetsOrder()); + const Stickers::Sets &sets(Global::StickerSets()); + for (int i = 0, l = order.size(); i < l; ++i) { + auto it = sets.constFind(order.at(i)); if (it != sets.cend()) { - if (!(it->flags & MTPDstickerSet::flag_disabled) || ((it->flags & MTPDstickerSet::flag_official) && includeDisabledOfficial)) { + if (!(it->flags & MTPDstickerSet::Flag::f_disabled) || ((it->flags & MTPDstickerSet::Flag::f_official) && includeDisabledOfficial)) { ++result; } } diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 901dd586b8..fc2c9a5138 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -29,10 +29,12 @@ public: StickerSetInner(const MTPInputStickerSet &set); - void init(); + void mousePressEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); void paintEvent(QPaintEvent *e); - + bool loaded() const; int32 notInstalled() const; bool official() const; @@ -42,10 +44,12 @@ public: void setScrollBottom(int32 bottom); void install(); - QString getTitle() const; - ~StickerSetInner(); +public slots: + + void onPreview(); + signals: void updateButtons(); @@ -53,6 +57,8 @@ signals: private: + int32 stickerFromGlobalPos(const QPoint &p) const; + void gotSet(const MTPmessages_StickerSet &set); bool failedSet(const RPCError &error); @@ -60,15 +66,20 @@ private: bool installFailed(const RPCError &error); StickerPack _pack; + StickersByEmojiMap _emoji; bool _loaded; uint64 _setId, _setAccess; QString _title, _setTitle, _setShortName; - int32 _setCount, _setHash, _setFlags; + int32 _setCount, _setHash; + MTPDstickerSet::Flags _setFlags; int32 _bottom; MTPInputStickerSet _input; mtpRequestId _installRequest; + + QTimer _previewTimer; + int32 _previewShown; }; class StickerSetBox : public ScrollableBox, public RPCSender { @@ -118,7 +129,7 @@ public: void mousePressEvent(QMouseEvent *e); void mouseMoveEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); - + void rebuild(); bool savingStart() { if (_saving) return false; @@ -201,7 +212,7 @@ public: StickersBox(); void resizeEvent(QResizeEvent *e); void paintEvent(QPaintEvent *e); - + void closePressed(); public slots: diff --git a/Telegram/SourceFiles/boxes/usernamebox.cpp b/Telegram/SourceFiles/boxes/usernamebox.cpp index 71e0a99481..247db630fc 100644 --- a/Telegram/SourceFiles/boxes/usernamebox.cpp +++ b/Telegram/SourceFiles/boxes/usernamebox.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "lang.h" @@ -160,7 +160,7 @@ void UsernameBox::onChanged() { } _checkTimer.stop(); } else { - int32 i, len = name.size(); + int32 len = name.size(); for (int32 i = 0; i < len; ++i) { QChar ch = name.at(i); if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '_' && (ch != '@' || i > 0)) { @@ -191,7 +191,7 @@ void UsernameBox::onChanged() { } void UsernameBox::onLinkClick() { - App::app()->clipboard()->setText(qsl("https://telegram.me/") + getName()); + Application::clipboard()->setText(qsl("https://telegram.me/") + getName()); _copiedTextLink = lang(lng_username_copied); update(); } diff --git a/Telegram/SourceFiles/boxes/usernamebox.h b/Telegram/SourceFiles/boxes/usernamebox.h index c48af468f9..d2dfe0b6e1 100644 --- a/Telegram/SourceFiles/boxes/usernamebox.h +++ b/Telegram/SourceFiles/boxes/usernamebox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/codegen/style/main.cpp b/Telegram/SourceFiles/codegen/style/main.cpp new file mode 100644 index 0000000000..a553b51734 --- /dev/null +++ b/Telegram/SourceFiles/codegen/style/main.cpp @@ -0,0 +1,3 @@ +int main(int argc, char *argv[]) { + return 0; +} diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index c3ebc74865..f01d03751c 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -16,14 +16,14 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -static const int32 AppVersion = 9017; -static const wchar_t *AppVersionStr = L"0.9.17"; +static const int32 AppVersion = 9040; +static const wchar_t *AppVersionStr = L"0.9.40"; static const bool DevVersion = false; -//#define BETA_VERSION (9015008ULL) // just comment this line to build public version +//#define BETA_VERSION (9034004ULL) // just comment this line to build public version static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)"; static const wchar_t *AppName = L"Telegram Desktop"; @@ -51,11 +51,11 @@ enum { MTPIPv4ConnectionWaitTimeout = 1000, // 1 seconds waiting for ipv4, until we accept ipv6 MTPMillerRabinIterCount = 30, // 30 Miller-Rabin iterations for dh_prime primality check - MTPUploadSessionsCount = 4, // max 4 upload sessions is created - MTPDownloadSessionsCount = 4, // max 4 download sessions is created + MTPUploadSessionsCount = 2, // max 2 upload sessions is created + MTPDownloadSessionsCount = 2, // max 2 download sessions is created MTPKillFileSessionTimeout = 5000, // how much time without upload / download causes additional session kill - MTPEnumDCTimeout = 4000, // 4 seconds timeout for help_getConfig to work (them move to other dc) + MTPEnumDCTimeout = 8000, // 8 seconds timeout for help_getConfig to work (then move to other dc) MTPDebugBufferSize = 1024 * 1024, // 1 mb start size @@ -101,6 +101,9 @@ enum { MediaOverviewStartPerPage = 5, MediaOverviewPreloadCount = 4, + // a new message from the same sender is attached to previous within 15 minutes + AttachMessageToPreviousSecondsDelta = 900, + AudioVoiceMsgSimultaneously = 4, AudioSongSimultaneously = 4, AudioCheckPositionTimeout = 100, // 100ms per check audio pos @@ -118,6 +121,8 @@ enum { AudioVoiceMsgInMemory = 2 * 1024 * 1024, // 2 Mb audio is hold in memory and auto loaded AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over + WaveformSamplesCount = 100, + StickerInMemory = 2 * 1024 * 1024, // 2 Mb stickers hold in memory, auto loaded and displayed inline StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker @@ -127,12 +132,13 @@ enum { MaxZoomLevel = 7, // x8 ZoomToScreenLevel = 1024, // just constant + ShortcutsCountLimit = 256, // how many shortcuts can be in json file + PreloadHeightsCount = 3, // when 3 screens to scroll left make a preload request EmojiPanPerRow = 7, EmojiPanRowsPerPage = 6, StickerPanPerRow = 5, StickerPanRowsPerPage = 4, - SavedGifsMaxPerRow = 4, StickersUpdateTimeout = 3600000, // update not more than once in an hour SearchPeopleLimit = 5, @@ -140,9 +146,9 @@ enum { MaxUsernameLength = 32, UsernameCheckTimeout = 200, - MaxChannelDescription = 120, + MaxChannelDescription = 255, MaxGroupChannelTitle = 255, - MaxPhotoCaption = 140, + MaxPhotoCaption = 200, MaxMessageSize = 4096, MaxHttpRedirects = 5, // when getting external data/images @@ -167,6 +173,8 @@ enum { ChoosePeerByDragTimeout = 1000, // 1 second mouse not moved to choose dialog when dragging a file ReloadChannelMembersTimeout = 1000, // 1 second wait before reload members in channel after adding + + PinnedMessageTextLimit = 16, }; inline bool isNotificationsUser(uint64 id) { @@ -191,7 +199,7 @@ inline const char *cGUIDStr() { return gGuidStr; } -inline const char **cPublicRSAKeys(uint32 &cnt) { +inline const char **cPublicRSAKeys(int &keysCount) { static const char *(keys[]) = {"\ -----BEGIN RSA PUBLIC KEY-----\n\ MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6\n\ @@ -201,7 +209,7 @@ Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+\n\ 8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n\n\ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB\n\ -----END RSA PUBLIC KEY-----"}; - cnt = sizeof(keys) / sizeof(const char*); + keysCount = arraysize(keys); return keys; } diff --git a/Telegram/SourceFiles/countries.h b/Telegram/SourceFiles/countries.h index 6255464ef3..135d03b9e4 100644 --- a/Telegram/SourceFiles/countries.h +++ b/Telegram/SourceFiles/countries.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index ef5247c88e..e7ba1fca3e 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -16,10 +16,10 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" +#include "gui/style.h" #include "lang.h" #include "application.h" @@ -247,18 +247,15 @@ void DialogsInner::peopleResultPaint(PeerData *peer, Painter &p, int32 w, bool a History *history = App::history(peer->id); - if (peer->migrateTo()) { - p.drawPixmap(st::dlgPaddingHor, st::dlgPaddingVer, peer->migrateTo()->photo->pix(st::dlgPhotoSize)); - } else { - p.drawPixmap(st::dlgPaddingHor, st::dlgPaddingVer, peer->photo->pix(st::dlgPhotoSize)); - } + PeerData *userpicPeer = (peer->migrateTo() ? peer->migrateTo() : peer); + userpicPeer->paintUserpicLeft(p, st::dlgPhotoSize, st::dlgPaddingHor, st::dlgPaddingVer, fullWidth()); int32 nameleft = st::dlgPaddingHor + st::dlgPhotoSize + st::dlgPhotoPadding; int32 namewidth = w - nameleft - st::dlgPaddingHor; QRect rectForName(nameleft, st::dlgPaddingVer + st::dlgNameTop, namewidth, st::msgNameFont->height); // draw chat icon - if (peer->isChat()) { + if (peer->isChat() || peer->isMegagroup()) { p.drawPixmap(QPoint(rectForName.left() + st::dlgChatImgPos.x(), rectForName.top() + st::dlgChatImgPos.y()), App::sprite(), (act ? st::dlgActiveChatImg : st::dlgChatImg)); rectForName.setLeft(rectForName.left() + st::dlgImgSkip); } else if (peer->isChannel()) { @@ -299,7 +296,7 @@ void DialogsInner::searchInPeerPaint(Painter &p, int32 w, bool onlyBackground) c p.fillRect(fullRect, st::dlgBG->b); if (onlyBackground) return; - p.drawPixmap(st::dlgPaddingHor, st::dlgPaddingVer, _searchInPeer->photo->pix(st::dlgPhotoSize)); + _searchInPeer->paintUserpicLeft(p, st::dlgPhotoSize, st::dlgPaddingHor, st::dlgPaddingVer, fullWidth()); int32 nameleft = st::dlgPaddingHor + st::dlgPhotoSize + st::dlgPhotoPadding; int32 namewidth = w - nameleft - st::dlgPaddingHor * 2 - st::btnCancelSearch.width; @@ -443,16 +440,12 @@ void DialogsInner::onDialogRowReplaced(DialogRow *oldRow, DialogRow *newRow) { } void DialogsInner::createDialog(History *history) { - bool creating = history->dialogs.isEmpty(); + bool creating = !history->inChatList(); if (creating) { - history->dialogs = dialogs.addToEnd(history); - contactsNoDialogs.del(history->peer, history->dialogs[0]); + DialogRow *mainRow = history->addToChatList(dialogs); + contactsNoDialogs.del(history->peer, mainRow); } - - History::DialogLinks links = history->dialogs; - int32 movedFrom = links[0]->pos * st::dlgHeight; - dialogs.adjustByPos(links); - int32 movedTo = links[0]->pos * st::dlgHeight; + RefPair(int32, movedFrom, int32, movedTo) = history->adjustByPosInChatsList(dialogs); emit dialogMoved(movedFrom, movedTo); @@ -471,8 +464,7 @@ void DialogsInner::removeDialog(History *history) { if (sel && sel->history == history) { sel = 0; } - dialogs.del(history->peer); - history->dialogs = History::DialogLinks(); + history->removeFromChatList(dialogs); history->clearNotifications(); if (App::wnd()) App::wnd()->notifyClear(history); if (contacts.list.rowByPeer.constFind(history->peer->id) != contacts.list.rowByPeer.cend()) { @@ -550,8 +542,8 @@ void DialogsInner::updateSelectedRow(PeerData *peer) { if (_state == DefaultState) { if (peer) { if (History *h = App::historyLoaded(peer->id)) { - if (h->dialogs.contains(0)) { - update(0, h->dialogs.value(0)->pos * st::dlgHeight, fullWidth(), st::dlgHeight); + if (h->inChatList()) { + update(0, h->posInChatList() * st::dlgHeight, fullWidth(), st::dlgHeight); } } } else if (sel) { @@ -624,7 +616,7 @@ void DialogsInner::contextMenuEvent(QContextMenuEvent *e) { if (_menuPeer->isUser()) { _menu->addAction(lang(lng_profile_clear_history), this, SLOT(onContextClearHistory()))->setEnabled(true); _menu->addAction(lang(lng_profile_delete_conversation), this, SLOT(onContextDeleteAndLeave()))->setEnabled(true); - if (_menuPeer->asUser()->access != UserNoAccess) { + if (_menuPeer->asUser()->access != UserNoAccess && _menuPeer != App::self()) { _menu->addAction(lang((_menuPeer->asUser()->blocked == UserIsBlocked) ? (_menuPeer->asUser()->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (_menuPeer->asUser()->botInfo ? lng_profile_block_bot : lng_profile_block_user)), this, SLOT(onContextToggleBlock()))->setEnabled(true); connect(App::main(), SIGNAL(peerUpdated(PeerData*)), this, SLOT(peerUpdated(PeerData*))); } @@ -651,7 +643,7 @@ void DialogsInner::onContextProfile() { void DialogsInner::onContextToggleNotifications() { if (!_menuPeer) return; - App::main()->updateNotifySetting(_menuPeer, menuPeerMuted()); + App::main()->updateNotifySetting(_menuPeer, menuPeerMuted() ? NotifySettingSetNotify : NotifySettingSetMuted); } void DialogsInner::onContextSearch() { @@ -963,7 +955,9 @@ void DialogsInner::dialogsReceived(const QVector &added) { case mtpc_dialog: { const MTPDdialog &d(i->c_dialog()); history = App::historyFromDialog(peerFromMTP(d.vpeer), d.vunread_count.v, d.vread_inbox_max_id.v); - App::main()->applyNotifySetting(MTP_notifyPeer(d.vpeer), d.vnotify_settings, history); + if (App::main()) { + App::main()->applyNotifySetting(MTP_notifyPeer(d.vpeer), d.vnotify_settings, history); + } } break; case mtpc_dialogChannel: { @@ -986,7 +980,9 @@ void DialogsInner::dialogsReceived(const QVector &added) { if (!history->isMegagroup() && d.vtop_message.v > d.vtop_important_message.v) { history->setNotLoadedAtBottom(); } - App::main()->applyNotifySetting(MTP_notifyPeer(d.vpeer), d.vnotify_settings, history); + if (App::main()) { + App::main()->applyNotifySetting(MTP_notifyPeer(d.vpeer), d.vnotify_settings, history); + } } break; } @@ -1015,7 +1011,7 @@ void DialogsInner::addSavedPeersAfter(const QDateTime &date) { SavedPeersByTime &saved(cRefSavedPeersByTime()); while (!saved.isEmpty() && (date.isNull() || date < saved.lastKey())) { History *history = App::history(saved.last()->id); - history->setPosInDialogsDate(saved.lastKey()); + history->setChatsListDate(saved.lastKey()); contactsNoDialogs.del(history->peer); saved.remove(saved.lastKey(), saved.last()); } @@ -1040,13 +1036,16 @@ bool DialogsInner::searchReceived(const QVector &messages, DialogsSe _lastSearchDate = lastDateFound; } } - if (type == DialogsSearchFromStart || type == DialogsSearchFromOffset) { - _lastSearchPeer = item->history()->peer; + if (item) { + if (type == DialogsSearchFromStart || type == DialogsSearchFromOffset) { + _lastSearchPeer = item->history()->peer; + } } + MsgId msgId = item ? item->id : idFromMessage(*i); if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { - _lastSearchMigratedId = item->id; + _lastSearchMigratedId = msgId; } else { - _lastSearchId = item->id; + _lastSearchId = msgId; } } if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { @@ -1067,8 +1066,11 @@ void DialogsInner::peopleReceived(const QString &query, const QVector & _peopleResults.reserve(people.size()); for (QVector::const_iterator i = people.cbegin(), e = people.cend(); i != e; ++i) { PeerId peerId = peerFromMTP(*i); - History *h = App::historyLoaded(peerId); - if (h && !h->dialogs.isEmpty()) continue; // skip dialogs + if (History *h = App::historyLoaded(peerId)) { + if (h->inChatList()) { + continue; // skip existing chats + } + } _peopleResults.push_back(App::peer(peerId)); } @@ -1359,6 +1361,8 @@ void DialogsInner::selectSkipPage(int32 pixels, int32 direction) { } void DialogsInner::loadPeerPhotos(int32 yFrom) { + if (!parentWidget()) return; + int32 yTo = yFrom + parentWidget()->height() * 5; MTP::clearLoaderPriorities(); if (_state == DefaultState) { @@ -1366,7 +1370,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) { if (yFrom < otherStart) { dialogs.list.adjustCurrent(yFrom, st::dlgHeight); for (DialogRow *row = dialogs.list.current; row != dialogs.list.end && (row->pos * st::dlgHeight) < yTo; row = row->next) { - row->history->peer->photo->load(); + row->history->peer->loadUserpic(); } yFrom = 0; } else { @@ -1376,7 +1380,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) { if (yTo > 0) { contactsNoDialogs.list.adjustCurrent(yFrom, st::dlgHeight); for (DialogRow *row = contactsNoDialogs.list.current; row != contactsNoDialogs.list.end && (row->pos * st::dlgHeight) < yTo; row = row->next) { - row->history->peer->photo->load(); + row->history->peer->loadUserpic(); } } } else if (_state == FilteredState || _state == SearchedState) { @@ -1387,7 +1391,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) { if (to > _filterResults.size()) to = _filterResults.size(); for (; from < to; ++from) { - _filterResults[from]->history->peer->photo->load(); + _filterResults[from]->history->peer->loadUserpic(); } } @@ -1398,7 +1402,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) { if (to > _peopleResults.size()) to = _peopleResults.size(); for (; from < to; ++from) { - _peopleResults[from]->photo->load(); + _peopleResults[from]->loadUserpic(); } } from = (yFrom > filteredOffset() + ((_peopleResults.isEmpty() ? 0 : st::searchedBarHeight) + st::searchedBarHeight) ? ((yFrom - filteredOffset() - (_peopleResults.isEmpty() ? 0 : st::searchedBarHeight) - st::searchedBarHeight) / int32(st::dlgHeight)) : 0) - _filterResults.size() - _peopleResults.size(); @@ -1408,7 +1412,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) { if (to > _searchResults.size()) to = _searchResults.size(); for (; from < to; ++from) { - _searchResults[from]->_item->history()->peer->photo->load(); + _searchResults[from]->_item->history()->peer->loadUserpic(); } } } @@ -1515,6 +1519,11 @@ void DialogsInner::destroyData() { } void DialogsInner::peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg) const { + if (!inPeer) { + outPeer = 0; + outMsg = 0; + return; + } if (_state == DefaultState) { DialogsList::RowByPeer::const_iterator i = dialogs.list.rowByPeer.constFind(inPeer->id); if (i == dialogs.list.rowByPeer.constEnd()) { @@ -1599,6 +1608,11 @@ void DialogsInner::peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&ou } void DialogsInner::peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg) const { + if (!inPeer) { + outPeer = 0; + outMsg = 0; + return; + } if (_state == DefaultState) { DialogsList::RowByPeer::const_iterator i = dialogs.list.rowByPeer.constFind(inPeer->id); if (i == dialogs.list.rowByPeer.constEnd()) { @@ -1777,11 +1791,11 @@ void DialogsWidget::activate() { } void DialogsWidget::createDialog(History *history) { - bool creating = history->dialogs.isEmpty(); + bool creating = !history->inChatList(); _inner.createDialog(history); if (creating && history->peer->migrateFrom()) { if (History *h = App::historyLoaded(history->peer->migrateFrom()->id)) { - if (!h->dialogs.isEmpty()) { + if (h->inChatList()) { removeDialog(h); } } @@ -2038,8 +2052,11 @@ bool DialogsWidget::onSearchMessages(bool searchCache) { MTP::cancel(_searchRequest); } if (_searchInPeer) { - int32 flags = (_searchInPeer->isChannel() && !_searchInPeer->isMegagroup()) ? MTPmessages_Search::flag_important_only : 0; - _searchRequest = MTP::send(MTPmessages_Search(MTP_int(flags), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart)); + MTPmessages_Search::Flags flags = 0; + if (_searchInPeer->isChannel() && !_searchInPeer->isMegagroup()) { + flags |= MTPmessages_Search::Flag::f_important_only; + } + _searchRequest = MTP::send(MTPmessages_Search(MTP_flags(flags), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart)); } else { _searchRequest = MTP::send(MTPmessages_SearchGlobal(MTP_string(_searchQuery), MTP_int(0), MTP_inputPeerEmpty(), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchFromStart)); } @@ -2077,6 +2094,7 @@ void DialogsWidget::onChooseByDrag() { void DialogsWidget::searchMessages(const QString &query, PeerData *inPeer) { if ((_filter.getLastText() != query) || (inPeer && inPeer != _searchInPeer && inPeer->migrateTo() != _searchInPeer)) { if (inPeer) { + onCancelSearch(); _searchInPeer = inPeer->migrateTo() ? inPeer->migrateTo() : inPeer; _searchInMigrated = _searchInPeer ? _searchInPeer->migrateFrom() : 0; _inner.searchInPeer(_searchInPeer); @@ -2098,8 +2116,11 @@ void DialogsWidget::onSearchMore() { PeerData *offsetPeer = _inner.lastSearchPeer(); MsgId offsetId = _inner.lastSearchId(); if (_searchInPeer) { - int32 flags = (_searchInPeer->isChannel() && !_searchInPeer->isMegagroup()) ? MTPmessages_Search::flag_important_only : 0; - _searchRequest = MTP::send(MTPmessages_Search(MTP_int(flags), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart)); + MTPmessages_Search::Flags flags = 0; + if (_searchInPeer->isChannel() && !_searchInPeer->isMegagroup()) { + flags |= MTPmessages_Search::Flag::f_important_only; + } + _searchRequest = MTP::send(MTPmessages_Search(MTP_flags(flags), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart)); } else { _searchRequest = MTP::send(MTPmessages_SearchGlobal(MTP_string(_searchQuery), MTP_int(offsetDate), offsetPeer ? offsetPeer->input : MTP_inputPeerEmpty(), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart)); } @@ -2108,8 +2129,11 @@ void DialogsWidget::onSearchMore() { } } else if (_searchInMigrated && !_searchFullMigrated) { MsgId offsetMigratedId = _inner.lastSearchMigratedId(); - int32 flags = (_searchInMigrated->isChannel() && !_searchInMigrated->isMegagroup()) ? MTPmessages_Search::flag_important_only : 0; - _searchRequest = MTP::send(MTPmessages_Search(MTP_int(flags), _searchInMigrated->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetMigratedId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart), rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart)); + MTPmessages_Search::Flags flags = 0; + if (_searchInMigrated->isChannel() && !_searchInMigrated->isMegagroup()) { + flags |= MTPmessages_Search::Flag::f_important_only; + } + _searchRequest = MTP::send(MTPmessages_Search(MTP_flags(flags), _searchInMigrated->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetMigratedId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart), rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart)); } } } @@ -2268,7 +2292,7 @@ void DialogsWidget::dragEnterEvent(QDragEnterEvent *e) { _dragForward = e->mimeData()->hasFormat(qsl("application/x-td-forward-selected")); if (!_dragForward) _dragForward = e->mimeData()->hasFormat(qsl("application/x-td-forward-pressed-link")); if (!_dragForward) _dragForward = e->mimeData()->hasFormat(qsl("application/x-td-forward-pressed")); - if (_dragForward && !cWideMode()) _dragForward = false; + if (_dragForward && Adaptive::OneColumn()) _dragForward = false; if (_dragForward) { e->setDropAction(Qt::CopyAction); e->accept(); @@ -2540,7 +2564,7 @@ bool DialogsWidget::onCancelSearch() { _searchRequest = 0; } if (_searchInPeer && !clearing) { - if (!cWideMode()) { + if (Adaptive::OneColumn()) { Ui::showPeerHistory(_searchInPeer, ShowAtUnreadMsgId); } _searchInPeer = _searchInMigrated = 0; @@ -2560,7 +2584,7 @@ void DialogsWidget::onCancelSearchInPeer() { _searchRequest = 0; } if (_searchInPeer) { - if (!cWideMode() && !App::main()->selectingPeer()) { + if (Adaptive::OneColumn() && !App::main()->selectingPeer()) { Ui::showPeerHistory(_searchInPeer, ShowAtUnreadMsgId); } _searchInPeer = _searchInMigrated = 0; @@ -2570,7 +2594,7 @@ void DialogsWidget::onCancelSearchInPeer() { _filter.clear(); _filter.updatePlaceholder(); onFilterUpdate(); - if (cWideMode() && !App::main()->selectingPeer()) { + if (!Adaptive::OneColumn() && !App::main()->selectingPeer()) { emit cancelled(); } } diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index 60cb877b93..75ef10e494 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -67,7 +67,6 @@ public: void selectSkipPage(int32 pixels, int32 direction); void createDialog(History *history); - void moveDialogToTop(const History::DialogLinks &links); void dlgUpdated(DialogRow *row); void dlgUpdated(History *row, MsgId msgId); void removeDialog(History *history); @@ -219,16 +218,16 @@ public: void contactsReceived(const MTPcontacts_Contacts &contacts); void searchReceived(DialogsSearchRequestType type, const MTPmessages_Messages &result, mtpRequestId req); void peopleReceived(const MTPcontacts_Found &result, mtpRequestId req); - - void dragEnterEvent(QDragEnterEvent *e); - void dragMoveEvent(QDragMoveEvent *e); - void dragLeaveEvent(QDragLeaveEvent *e); - void dropEvent(QDropEvent *e); + + void dragEnterEvent(QDragEnterEvent *e) override; + void dragMoveEvent(QDragMoveEvent *e) override; + void dragLeaveEvent(QDragLeaveEvent *e) override; + void dropEvent(QDropEvent *e) override; void updateDragInScroll(bool inScroll); - void resizeEvent(QResizeEvent *e); - void keyPressEvent(QKeyEvent *e); - void paintEvent(QPaintEvent *e); + void resizeEvent(QResizeEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void paintEvent(QPaintEvent *e) override; void searchInPeer(PeerData *peer); @@ -260,6 +259,11 @@ public: void updateNotifySettings(PeerData *peer); + void rpcClear() override { + _inner.rpcClear(); + RPCSender::rpcClear(); + } + void notify_userIsContactChanged(UserData *user, bool fromThisApp); signals: diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 87d9fd97b2..4c42ab32ab 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -16,22 +16,21 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" - #include "dropdown.h" -#include "historywidget.h" - -#include "localstorage.h" -#include "lang.h" - -#include "window.h" -#include "apiwrap.h" -#include "mainwidget.h" #include "boxes/confirmbox.h" #include "boxes/stickersetbox.h" +#include "inline_bots/inline_bot_result.h" +#include "inline_bots/inline_bot_layout_item.h" +#include "historywidget.h" +#include "localstorage.h" +#include "lang.h" +#include "window.h" +#include "apiwrap.h" +#include "mainwidget.h" Dropdown::Dropdown(QWidget *parent, const style::dropdown &st) : TWidget(parent) , _ignore(false) @@ -457,6 +456,8 @@ void DragArea::step_appearance(float64 ms, bool timer) { if (timer) update(); } +namespace internal { + EmojiColorPicker::EmojiColorPicker() : TWidget() , _ignoreShow(false) , _a_selected(animation(this, &EmojiColorPicker::step_selected)) @@ -941,6 +942,8 @@ void EmojiPanInner::selectEmoji(EmojiPtr emoji) { } void EmojiPanInner::onShowPicker() { + if (_pickerSel < 0) return; + int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift; if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { int32 y = 0; @@ -1097,7 +1100,7 @@ void EmojiPanInner::fillPanels(QVector &panels) { int y = 0; panels.reserve(emojiTabCount); for (int c = 0; c < emojiTabCount; ++c) { - panels.push_back(new EmojiPanel(parentWidget(), lang(LangKey(lng_emoji_category0 + c)), NoneStickerSetId, true, y)); + panels.push_back(new EmojiPanel(parentWidget(), lang(LangKey(lng_emoji_category0 + c)), Stickers::NoneSetId, true, y)); connect(panels.back(), SIGNAL(mousePressed()), this, SLOT(checkPickerHide())); int cnt = _counts[c], rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0); panels.back()->show(); @@ -1270,6 +1273,12 @@ int32 StickerPanInner::countHeight(bool plain) { return qMax(minLastH, result) + st::stickerPanPadding; } +StickerPanInner::~StickerPanInner() { + clearInlineRows(true); + deleteUnusedGifLayouts(); + deleteUnusedInlineLayouts(); +} + QRect StickerPanInner::stickerRect(int tab, int sel) { int x = 0, y = 0; for (int i = 0; i < _sets.size(); ++i) { @@ -1309,7 +1318,7 @@ void StickerPanInner::paintInlineItems(Painter &p, const QRect &r) { p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), lang(lng_inline_bot_no_results), style::al_center); return; } - InlinePaintContext context(getms(), false, Ui::isLayerShown() || Ui::isMediaViewShown() || _previewShown, false); + InlineBots::Layout::PaintContext context(getms(), false, Ui::isLayerShown() || Ui::isMediaViewShown() || _previewShown, false); int32 top = st::emojiPanHeader; int32 fromx = rtl() ? (width() - r.x() - r.width()) : r.x(), tox = rtl() ? (width() - r.x()) : (r.x() + r.width()); @@ -1322,13 +1331,17 @@ void StickerPanInner::paintInlineItems(Painter &p, const QRect &r) { for (int32 col = 0, cols = inlineRow.items.size(); col < cols; ++col) { if (left >= tox) break; - int32 w = inlineRow.items.at(col)->width(); + const InlineItem *item = inlineRow.items.at(col); + int32 w = item->width(); if (left + w > fromx) { p.translate(left, top); - inlineRow.items.at(col)->paint(p, r.translated(-left, -top), 0, &context); + item->paint(p, r.translated(-left, -top), 0, &context); p.translate(-left, -top); } - left += w + st::inlineResultsSkip; + left += w; + if (item->hasRightSkip()) { + left += st::inlineResultsSkip; + } } } top += inlineRow.height; @@ -1352,7 +1365,7 @@ void StickerPanInner::paintStickers(Painter &p, const QRect &r) { tilly = y + st::emojiPanHeader + (rows * st::stickerPanSize.height()); if (r.top() >= tilly) continue; - bool special = (_sets[c].flags & MTPDstickerSet::flag_official); + bool special = (_sets[c].flags & MTPDstickerSet::Flag::f_official); y += st::emojiPanHeader; int32 fromrow = floorclamp(r.y() - y, st::stickerPanSize.height(), 0, rows); @@ -1395,7 +1408,7 @@ void StickerPanInner::paintStickers(Painter &p, const QRect &r) { p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h)); } - if (hover > 0 && _sets[c].id == RecentStickerSetId && _custom.at(index)) { + if (hover > 0 && _sets[c].id == Stickers::RecentSetId && _custom.at(index)) { float64 xHover = _sets[c].hovers[_sets[c].pack.size() + index]; QPoint xPos = pos + QPoint(st::stickerPanSize.width() - st::stickerPanDelete.pxWidth(), 0); @@ -1413,8 +1426,7 @@ void StickerPanInner::mousePressEvent(QMouseEvent *e) { updateSelected(); _pressedSel = _selected; - textlnkDown(textlnkOver()); - _linkDown = _linkOver; + ClickHandler::pressed(); _previewTimer.start(QApplication::startDragTime()); } @@ -1422,10 +1434,9 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { _previewTimer.stop(); int32 pressed = _pressedSel; - TextLinkPtr down(_linkDown); _pressedSel = -1; - _linkDown.reset(); - textlnkDown(TextLinkPtr()); + + ClickHandlerPtr activated = ClickHandler::unpressed(); _lastMousePos = e->globalPos(); updateSelected(); @@ -1435,71 +1446,36 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { return; } - if (_selected < 0 || _selected != pressed || _linkOver != down) return; + if (_selected < 0 || _selected != pressed || (_showingInlineItems && !activated)) return; if (_showingInlineItems) { - int32 row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; - if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { - if (down) { - if (down->type() == qstr("SendInlineItemLink") && e->button() == Qt::LeftButton) { - LayoutInlineItem *item = _inlineRows.at(row).items.at(col); - PhotoData *photo = item->photo(); - DocumentData *doc = item->document(); - InlineResult *result = item->result(); - if (doc) { - if (doc->loaded()) { - emit selected(doc); - } else if (doc->loading()) { - doc->cancel(); - } else { - DocumentOpenLink::doOpen(doc, ActionOnLoadNone); - } - } else if (photo) { - if (photo->medium->loaded() || photo->thumb->loaded()) { - emit selected(photo); - } else if (!photo->medium->loading()) { - photo->thumb->loadEvenCancelled(); - photo->medium->loadEvenCancelled(); - } - } else if (result) { - if (result->type == qstr("gif")) { - if (result->doc) { - if (result->doc->loaded()) { - emit selected(result, _inlineBot); - } else if (result->doc->loading()) { - result->doc->cancel(); - } else { - DocumentOpenLink::doOpen(result->doc, ActionOnLoadNone); - } - } else if (result->loaded()) { - emit selected(result, _inlineBot); - } else if (result->loading()) { - result->cancelFile(); - Ui::repaintInlineItem(item); - } else { - result->saveFile(QString(), LoadFromCloudOrLocal, false); - Ui::repaintInlineItem(item); - } - } else if (result->type == qstr("photo")) { - if (result->photo) { - if (result->photo->medium->loaded() || result->photo->thumb->loaded()) { - emit selected(result, _inlineBot); - } else if (!result->photo->medium->loading()) { - result->photo->thumb->loadEvenCancelled(); - result->photo->medium->loadEvenCancelled(); - } - } else if (result->thumb->loaded()) { - emit selected(result, _inlineBot); - } else if (!result->thumb->loading()) { - result->thumb->loadEvenCancelled(); - Ui::repaintInlineItem(item); - } - } else { - emit selected(result, _inlineBot); - } - } - } else { - down->onClick(e->button()); - } + if (!dynamic_cast(activated.data())) { + App::activateClickHandler(activated, e->button()); + return; + } + int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; + if (row >= _inlineRows.size() || col >= _inlineRows.at(row).items.size()) { + return; + } + + InlineItem *item = _inlineRows.at(row).items.at(col); + if (PhotoData *photo = item->getPhoto()) { + if (photo->medium->loaded() || photo->thumb->loaded()) { + emit selected(photo); + } else if (!photo->medium->loading()) { + photo->thumb->loadEvenCancelled(); + photo->medium->loadEvenCancelled(); + } + } else if (DocumentData *document = item->getDocument()) { + if (document->loaded()) { + emit selected(document); + } else if (document->loading()) { + document->cancel(); + } else { + DocumentOpenClickHandler::doOpen(document, ActionOnLoadNone); + } + } else if (InlineResult *inlineResult = item->getResult()) { + if (inlineResult->onChoose(item)) { + emit selected(inlineResult, _inlineBot); } } return; @@ -1509,7 +1485,7 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { } int tab = (_selected / MatrixRowShift), sel = _selected % MatrixRowShift; - if (_sets[tab].id == RecentStickerSetId && sel >= _sets[tab].pack.size() && sel < _sets[tab].pack.size() * 2 && _custom.at(sel - _sets[tab].pack.size())) { + if (_sets[tab].id == Stickers::RecentSetId && sel >= _sets[tab].pack.size() && sel < _sets[tab].pack.size() * 2 && _custom.at(sel - _sets[tab].pack.size())) { clearSelection(true); bool refresh = false; DocumentData *sticker = _sets[tab].pack.at(sel - _sets[tab].pack.size()); @@ -1522,8 +1498,8 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { break; } } - StickerSets &sets(cRefStickerSets()); - StickerSets::iterator it = sets.find(CustomStickerSetId); + Stickers::Sets &sets(Global::RefStickerSets()); + auto it = sets.find(Stickers::CustomSetId); if (it != sets.cend()) { for (int32 i = 0, l = it->stickers.size(); i < l; ++i) { if (it->stickers.at(i) == sticker) { @@ -1578,11 +1554,7 @@ void StickerPanInner::clearSelection(bool fast) { if (_selected >= 0) { int32 srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); - if (_linkOver) { - _inlineRows.at(srow).items.at(scol)->linkOut(_linkOver); - _linkOver = TextLinkPtr(); - textlnkOver(_linkOver); - } + ClickHandler::clearActive(_inlineRows.at(srow).items.at(scol)); setCursor(style::cur_default); } _selected = _pressedSel = -1; @@ -1595,7 +1567,7 @@ void StickerPanInner::clearSelection(bool fast) { _animations.clear(); if (_selected >= 0) { int index = qAbs(_selected), tab = (index / MatrixRowShift), sel = index % MatrixRowShift; - if (index >= 0 && tab < _sets.size() && _sets[tab].id == RecentStickerSetId && sel >= tab * MatrixRowShift + _sets[tab].pack.size()) { + if (index >= 0 && tab < _sets.size() && _sets[tab].id == Stickers::RecentSetId && sel >= tab * MatrixRowShift + _sets[tab].pack.size()) { _sets[tab].hovers[sel] = 0; sel -= _sets[tab].pack.size(); } @@ -1603,7 +1575,7 @@ void StickerPanInner::clearSelection(bool fast) { } if (_pressedSel >= 0) { int index = qAbs(_pressedSel), tab = (index / MatrixRowShift), sel = index % MatrixRowShift; - if (index >= 0 && tab < _sets.size() && _sets[tab].id == RecentStickerSetId && sel >= tab * MatrixRowShift + _sets[tab].pack.size()) { + if (index >= 0 && tab < _sets.size() && _sets[tab].id == Stickers::RecentSetId && sel >= tab * MatrixRowShift + _sets[tab].pack.size()) { _sets[tab].hovers[sel] = 0; sel -= _sets[tab].pack.size(); } @@ -1618,17 +1590,23 @@ void StickerPanInner::clearSelection(bool fast) { void StickerPanInner::hideFinish(bool completely) { if (completely) { + auto itemForget = [](const InlineItem *item) { + if (DocumentData *document = item->getDocument()) { + document->forget(); + } + if (PhotoData *photo = item->getPhoto()) { + photo->forget(); + } + if (InlineResult *result = item->getResult()) { + result->forget(); + } + }; clearInlineRows(false); - for (GifLayouts::const_iterator i = _gifLayouts.cbegin(), e = _gifLayouts.cend(); i != e; ++i) { - i.value()->document()->forget(); + for_const (InlineItem *item, _gifLayouts) { + itemForget(item); } - for (InlineLayouts::const_iterator i = _inlineLayouts.cbegin(), e = _inlineLayouts.cend(); i != e; ++i) { - if (i.value()->result()->doc) { - i.value()->result()->doc->forget(); - } - if (i.value()->result()->photo) { - i.value()->result()->photo->forget(); - } + for_const (InlineItem *item, _inlineLayouts) { + itemForget(item); } } if (_setGifCommand && _showingSavedGifs) { @@ -1640,11 +1618,11 @@ void StickerPanInner::hideFinish(bool completely) { void StickerPanInner::refreshStickers() { clearSelection(true); - const StickerSets &sets(cStickerSets()); + const Stickers::Sets &sets(Global::StickerSets()); _sets.clear(); _sets.reserve(sets.size() + 1); refreshRecentStickers(false); - for (StickerSetsOrder::const_iterator i = cStickerSetsOrder().cbegin(), e = cStickerSetsOrder().cend(); i != e; ++i) { + for (auto i = Global::StickerSetsOrder().cbegin(), e = Global::StickerSetsOrder().cend(); i != e; ++i) { appendSet(*i); } @@ -1664,7 +1642,7 @@ void StickerPanInner::refreshStickers() { } bool StickerPanInner::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth) { - LayoutInlineItem *layout = 0; + InlineItem *layout = nullptr; if (savedGif) { layout = layoutPrepareSavedGif(savedGif, (_inlineRows.size() * MatrixRowShift) + row.items.size()); } else if (result) { @@ -1673,23 +1651,28 @@ bool StickerPanInner::inlineRowsAddItem(DocumentData *savedGif, InlineResult *re if (!layout) return false; layout->preload(); - if (inlineRowFinalize(row, sumWidth, layout->fullLine())) { + if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { layout->setPosition(_inlineRows.size() * MatrixRowShift); } - row.items.push_back(layout); + sumWidth += layout->maxWidth(); + if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { + sumWidth += st::inlineResultsSkip; + } + + row.items.push_back(layout); return true; } bool StickerPanInner::inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force) { if (row.items.isEmpty()) return false; - bool full = (row.items.size() >= SavedGifsMaxPerRow); - bool big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - (row.items.size() - 1) * st::inlineResultsSkip); + bool full = (row.items.size() >= InlineItemsMaxPerRow); + bool big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); if (full || big || force) { _inlineRows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); row = InlineRow(); - row.items.reserve(SavedGifsMaxPerRow); + row.items.reserve(InlineItemsMaxPerRow); sumWidth = 0; return true; } @@ -1698,16 +1681,17 @@ bool StickerPanInner::inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool fo void StickerPanInner::refreshSavedGifs() { if (_showingSavedGifs) { + _settings.hide(); clearInlineRows(false); if (_showingInlineItems) { const SavedGifs &saved(cSavedGifs()); if (saved.isEmpty()) { - showStickerSet(RecentStickerSetId); + showStickerSet(Stickers::RecentSetId); return; } else { _inlineRows.reserve(saved.size()); InlineRow row; - row.items.reserve(SavedGifsMaxPerRow); + row.items.reserve(InlineItemsMaxPerRow); int32 sumWidth = 0; for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { inlineRowsAddItem(*i, 0, row, sumWidth); @@ -1751,37 +1735,33 @@ void StickerPanInner::clearInlineRows(bool resultsDeleted) { _inlineRows.clear(); } -LayoutInlineGif *StickerPanInner::layoutPrepareSavedGif(DocumentData *doc, int32 position) { - GifLayouts::const_iterator i = _gifLayouts.constFind(doc); +InlineItem *StickerPanInner::layoutPrepareSavedGif(DocumentData *doc, int32 position) { + auto i = _gifLayouts.constFind(doc); if (i == _gifLayouts.cend()) { - i = _gifLayouts.insert(doc, new LayoutInlineGif(0, doc, true)); - i.value()->initDimensions(); + if (auto layout = InlineItem::createLayoutGif(doc)) { + i = _gifLayouts.insert(doc, layout.release()); + i.value()->initDimensions(); + } else { + return nullptr; + } } - if (!i.value()->maxWidth()) return 0; + if (!i.value()->maxWidth()) return nullptr; i.value()->setPosition(position); return i.value(); } -LayoutInlineItem *StickerPanInner::layoutPrepareInlineResult(InlineResult *result, int32 position) { - InlineLayouts::const_iterator i = _inlineLayouts.constFind(result); +InlineItem *StickerPanInner::layoutPrepareInlineResult(InlineResult *result, int32 position) { + auto i = _inlineLayouts.constFind(result); if (i == _inlineLayouts.cend()) { - LayoutInlineItem *layout = 0; - if (result->type == qstr("gif")) { - layout = new LayoutInlineGif(result, 0, false); - } else if (result->type == qstr("photo")) { - layout = new LayoutInlinePhoto(result, 0); - } else if (result->type == qstr("video")) { - layout = new LayoutInlineWebVideo(result); - } else if (result->type == qstr("article")) { - layout = new LayoutInlineArticle(result, _inlineWithThumb); + if (auto layout = InlineItem::createLayout(result, _inlineWithThumb)) { + i = _inlineLayouts.insert(result, layout.release()); + i.value()->initDimensions(); + } else { + return nullptr; } - if (!layout) return 0; - - i = _inlineLayouts.insert(result, layout); - layout->initDimensions(); } - if (!i.value()->maxWidth()) return 0; + if (!i.value()->maxWidth()) return nullptr; i.value()->setPosition(position); return i.value(); @@ -1789,8 +1769,8 @@ LayoutInlineItem *StickerPanInner::layoutPrepareInlineResult(InlineResult *resul void StickerPanInner::deleteUnusedGifLayouts() { if (_inlineRows.isEmpty() || !_showingSavedGifs) { // delete all - for (GifLayouts::const_iterator i = _gifLayouts.cbegin(), e = _gifLayouts.cend(); i != e; ++i) { - delete i.value(); + for_const (const InlineItem *item, _gifLayouts) { + delete item; } _gifLayouts.clear(); } else { @@ -1807,8 +1787,8 @@ void StickerPanInner::deleteUnusedGifLayouts() { void StickerPanInner::deleteUnusedInlineLayouts() { if (_inlineRows.isEmpty() || _showingSavedGifs) { // delete all - for (InlineLayouts::const_iterator i = _inlineLayouts.cbegin(), e = _inlineLayouts.cend(); i != e; ++i) { - delete i.value(); + for_const (const InlineItem *item, _inlineLayouts) { + delete item; } _inlineLayouts.clear(); } else { @@ -1825,17 +1805,32 @@ void StickerPanInner::deleteUnusedInlineLayouts() { StickerPanInner::InlineRow &StickerPanInner::layoutInlineRow(InlineRow &row, int32 sumWidth) { int32 count = row.items.size(); - t_assert(count <= SavedGifsMaxPerRow); + t_assert(count <= InlineItemsMaxPerRow); + + // enumerate items in the order of growing maxWidth() + // for that sort item indices by maxWidth() + int indices[InlineItemsMaxPerRow]; + for (int i = 0; i < count; ++i) { + indices[i] = i; + } + std::sort(indices, indices + count, [&row](int a, int b) -> bool { + return row.items.at(a)->maxWidth() < row.items.at(b)->maxWidth(); + }); row.height = 0; - int32 availw = width() - st::inlineResultsLeft - st::inlineResultsSkip * (count - 1); - for (int32 i = 0; i < count; ++i) { - int32 w = sumWidth ? (row.items.at(i)->maxWidth() * availw / sumWidth) : row.items.at(i)->maxWidth(); - int32 actualw = qMax(w, int32(st::inlineResultsMinWidth)); - row.height = qMax(row.height, row.items.at(i)->resizeGetHeight(actualw)); + int availw = width() - st::inlineResultsLeft; + for (int i = 0; i < count; ++i) { + int index = indices[i]; + int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth(); + int actualw = qMax(w, int(st::inlineResultsMinWidth)); + row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw)); if (sumWidth) { availw -= actualw; - sumWidth -= row.items.at(i)->maxWidth(); + sumWidth -= row.items.at(index)->maxWidth(); + if (index > 0 && row.items.at(index - 1)->hasRightSkip()) { + availw -= st::inlineResultsSkip; + sumWidth -= st::inlineResultsSkip; + } } } return row; @@ -1870,7 +1865,7 @@ void StickerPanInner::preloadImages() { } uint64 StickerPanInner::currentSet(int yOffset) const { - if (_showingInlineItems) return NoneStickerSetId; + if (_showingInlineItems) return Stickers::NoneSetId; int y, ytill = 0; for (int i = 0, l = _sets.size(); i < l; ++i) { @@ -1881,7 +1876,7 @@ uint64 StickerPanInner::currentSet(int yOffset) const { return _sets.at(i).id; } } - return _sets.isEmpty() ? RecentStickerSetId : _sets.back().id; + return _sets.isEmpty() ? Stickers::RecentSetId : _sets.back().id; } void StickerPanInner::hideInlineRowsPanel() { @@ -1893,7 +1888,7 @@ void StickerPanInner::hideInlineRowsPanel() { emit scrollToY(0); emit scrollUpdated(); } else { - showStickerSet(RecentStickerSetId); + showStickerSet(Stickers::RecentSetId); } } } @@ -1912,22 +1907,21 @@ int32 StickerPanInner::refreshInlineRows(UserData *bot, const InlineResults &res return 0; } - if (_showingInlineItems) { - clearSelection(true); - } + clearSelection(true); t_assert(_inlineBot != 0); _inlineBotTitle = lng_inline_bot_results(lt_inline_bot, _inlineBot->username.isEmpty() ? _inlineBot->name : ('@' + _inlineBot->username)); _showingInlineItems = true; _showingSavedGifs = false; + _settings.hide(); int32 count = results.size(), from = validateExistingInlineRows(results), added = 0; if (count) { _inlineRows.reserve(count); InlineRow row; - row.items.reserve(SavedGifsMaxPerRow); + row.items.reserve(InlineItemsMaxPerRow); int32 sumWidth = 0; for (int32 i = from; i < count; ++i) { if (inlineRowsAddItem(0, results.at(i), row, sumWidth)) { @@ -1942,10 +1936,9 @@ int32 StickerPanInner::refreshInlineRows(UserData *bot, const InlineResults &res update(); emit refreshIcons(); - if (_showingInlineItems) { - _lastMousePos = QCursor::pos(); - updateSelected(); - } + + _lastMousePos = QCursor::pos(); + updateSelected(); return added; } @@ -1953,7 +1946,7 @@ int32 StickerPanInner::refreshInlineRows(UserData *bot, const InlineResults &res int32 StickerPanInner::validateExistingInlineRows(const InlineResults &results) { int32 count = results.size(), until = 0, untilrow = 0, untilcol = 0; for (; until < count;) { - if (untilrow >= _inlineRows.size() || _inlineRows.at(untilrow).items.at(untilcol)->result() != results.at(until)) { + if (untilrow >= _inlineRows.size() || _inlineRows.at(untilrow).items.at(untilcol)->getResult() != results.at(until)) { break; } ++until; @@ -2001,7 +1994,7 @@ int32 StickerPanInner::validateExistingInlineRows(const InlineResults &results) if (_inlineRows.isEmpty()) { _inlineWithThumb = false; for (int32 i = until; i < count; ++i) { - if (!results.at(i)->thumb->isNull()) { + if (results.at(i)->hasThumbDisplay()) { _inlineWithThumb = true; break; } @@ -2010,7 +2003,7 @@ int32 StickerPanInner::validateExistingInlineRows(const InlineResults &results) return until; } -void StickerPanInner::ui_repaintInlineItem(const LayoutInlineItem *layout) { +void StickerPanInner::ui_repaintInlineItem(const InlineItem *layout) { uint64 ms = getms(); if (_lastScrolled + 100 <= ms) { update(); @@ -2019,7 +2012,7 @@ void StickerPanInner::ui_repaintInlineItem(const LayoutInlineItem *layout) { } } -bool StickerPanInner::ui_isInlineItemVisible(const LayoutInlineItem *layout) { +bool StickerPanInner::ui_isInlineItemVisible(const InlineItem *layout) { int32 position = layout->position(); if (!_showingInlineItems || position < 0) return false; @@ -2040,9 +2033,9 @@ bool StickerPanInner::ui_isInlineItemBeingChosen() { } void StickerPanInner::appendSet(uint64 setId) { - const StickerSets &sets(cStickerSets()); - StickerSets::const_iterator it = sets.constFind(setId); - if (it == sets.cend() || (it->flags & MTPDstickerSet::flag_disabled) || it->stickers.isEmpty()) return; + const Stickers::Sets &sets(Global::StickerSets()); + auto it = sets.constFind(setId); + if (it == sets.cend() || (it->flags & MTPDstickerSet::Flag::f_disabled) || it->stickers.isEmpty()) return; StickerPack pack; pack.reserve(it->stickers.size()); @@ -2065,14 +2058,14 @@ void StickerPanInner::refreshRecent() { void StickerPanInner::refreshRecentStickers(bool performResize) { _custom.clear(); clearSelection(true); - StickerSets::const_iterator customIt = cStickerSets().constFind(CustomStickerSetId); - if (cGetRecentStickers().isEmpty() && (customIt == cStickerSets().cend() || customIt->stickers.isEmpty())) { - if (!_sets.isEmpty() && _sets.at(0).id == RecentStickerSetId) { + auto customIt = Global::StickerSets().constFind(Stickers::CustomSetId); + if (cGetRecentStickers().isEmpty() && (customIt == Global::StickerSets().cend() || customIt->stickers.isEmpty())) { + if (!_sets.isEmpty() && _sets.at(0).id == Stickers::RecentSetId) { _sets.pop_front(); } } else { StickerPack recent; - int32 customCnt = (customIt == cStickerSets().cend() ? 0 : customIt->stickers.size()); + int32 customCnt = (customIt == Global::StickerSets().cend() ? 0 : customIt->stickers.size()); QMap recentOnly; recent.reserve(cGetRecentStickers().size() + customCnt); _custom.reserve(cGetRecentStickers().size() + customCnt); @@ -2091,8 +2084,8 @@ void StickerPanInner::refreshRecentStickers(bool performResize) { _custom.push_back(true); } } - if (_sets.isEmpty() || _sets.at(0).id != RecentStickerSetId) { - _sets.push_back(DisplayedSet(RecentStickerSetId, MTPDstickerSet::flag_official, lang(lng_emoji_category0), recent.size() * 2, recent)); + if (_sets.isEmpty() || _sets.at(0).id != Stickers::RecentSetId) { + _sets.push_back(DisplayedSet(Stickers::RecentSetId, MTPDstickerSet::Flag::f_official, lang(lng_emoji_category0), recent.size() * 2, recent)); } else { _sets[0].pack = recent; _sets[0].hovers.resize(recent.size() * 2); @@ -2113,12 +2106,12 @@ void StickerPanInner::refreshRecentStickers(bool performResize) { void StickerPanInner::fillIcons(QList &icons) { icons.clear(); icons.reserve(_sets.size() + 1); - if (!cSavedGifs().isEmpty()) icons.push_back(StickerIcon(NoneStickerSetId)); + if (!cSavedGifs().isEmpty()) icons.push_back(StickerIcon(Stickers::NoneSetId)); if (_sets.isEmpty()) return; int32 i = 0; - if (_sets.at(0).id == RecentStickerSetId) ++i; - if (i > 0) icons.push_back(StickerIcon(RecentStickerSetId)); + if (_sets.at(0).id == Stickers::RecentSetId) ++i; + if (i > 0) icons.push_back(StickerIcon(Stickers::RecentSetId)); for (int32 l = _sets.size(); i < l; ++i) { DocumentData *s = _sets.at(i).pack.at(0); int32 availw = st::rbEmoji.width - 2 * st::stickerIconPadding, availh = st::rbEmoji.height - 2 * st::stickerIconPadding; @@ -2144,7 +2137,7 @@ void StickerPanInner::fillPanels(QVector &panels) { panels.clear(); if (_showingInlineItems) { - panels.push_back(new EmojiPanel(parentWidget(), _showingSavedGifs ? lang(lng_saved_gifs) : _inlineBotTitle, NoneStickerSetId, true, 0)); + panels.push_back(new EmojiPanel(parentWidget(), _showingSavedGifs ? lang(lng_saved_gifs) : _inlineBotTitle, Stickers::NoneSetId, true, 0)); panels.back()->show(); return; } @@ -2154,7 +2147,7 @@ void StickerPanInner::fillPanels(QVector &panels) { int y = 0; panels.reserve(_sets.size()); for (int32 i = 0, l = _sets.size(); i < l; ++i) { - bool special = (_sets.at(i).flags & MTPDstickerSet::flag_official); + bool special = (_sets.at(i).flags & MTPDstickerSet::Flag::f_official); panels.push_back(new EmojiPanel(parentWidget(), _sets.at(i).title, _sets.at(i).id, special, y)); panels.back()->show(); connect(panels.back(), SIGNAL(deleteClicked(quint64)), this, SIGNAL(removing(quint64))); @@ -2179,7 +2172,9 @@ void StickerPanInner::refreshPanels(QVector &panels) { } void StickerPanInner::updateSelected() { - if (_pressedSel >= 0 && !_previewShown) return; + if (_pressedSel >= 0 && !_previewShown) { + return; + } int32 selIndex = -1; QPoint p(mapFromGlobal(_lastMousePos)); @@ -2187,7 +2182,8 @@ void StickerPanInner::updateSelected() { if (_showingInlineItems) { int sx = (rtl() ? width() - p.x() : p.x()) - st::inlineResultsLeft, sy = p.y() - st::emojiPanHeader; int32 row = -1, col = -1, sel = -1; - TextLinkPtr lnk; + ClickHandlerPtr lnk; + ClickHandlerHost *lnkhost = nullptr; HistoryCursorState cursor = HistoryDefaultCursorState; if (sy >= 0) { row = 0; @@ -2206,11 +2202,15 @@ void StickerPanInner::updateSelected() { if (sx < width) { break; } - sx -= width + st::inlineResultsSkip; + sx -= width; + if (inlineItems.at(col)->hasRightSkip()) { + sx -= st::inlineResultsSkip; + } } if (col < inlineItems.size()) { sel = row * MatrixRowShift + col; inlineItems.at(col)->getState(lnk, cursor, sx, sy); + lnkhost = inlineItems.at(col); } else { row = col = -1; } @@ -2231,28 +2231,15 @@ void StickerPanInner::updateSelected() { } if (_pressedSel >= 0 && _selected >= 0 && _pressedSel != _selected) { _pressedSel = _selected; - if (row >= 0 && col >= 0 && _inlineRows.at(row).items.at(col)->document()) { - Ui::showStickerPreview(_inlineRows.at(row).items.at(col)->document()); + if (row >= 0 && col >= 0) { + if (DocumentData *previewDocument = _inlineRows.at(row).items.at(col)->getPreviewDocument()) { + Ui::showStickerPreview(previewDocument); + } } } } - if (_linkOver != lnk) { - if (_linkOver && srow >= 0 && scol >= 0) { - t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); - _inlineRows.at(srow).items.at(scol)->linkOut(_linkOver); - Ui::repaintInlineItem(_inlineRows.at(srow).items.at(scol)); - } - if ((_linkOver && !lnk) || (!_linkOver && lnk)) { - setCursor(lnk ? style::cur_pointer : style::cur_default); - } - _linkOver = lnk; - textlnkOver(lnk); - if (_linkOver && row >= 0 && col >= 0) { - t_assert(row >= 0 && row < _inlineRows.size() && col >= 0 && col < _inlineRows.at(row).items.size()); - _inlineRows.at(row).items.at(col)->linkOver(_linkOver); - Ui::repaintInlineItem(_inlineRows.at(row).items.at(col)); - } - + if (ClickHandler::setActive(lnk, lnkhost)) { + setCursor(lnk ? style::cur_pointer : style::cur_default); } return; } @@ -2261,7 +2248,7 @@ void StickerPanInner::updateSelected() { for (int c = 0, l = _sets.size(); c < l; ++c) { const DisplayedSet &set(_sets.at(c)); int cnt = set.pack.size(); - bool special = (set.flags & MTPDstickerSet::flag_official); + bool special = (set.flags & MTPDstickerSet::Flag::f_official); y = ytill; ytill = y + st::emojiPanHeader + ((cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0)) * st::stickerPanSize.height(); @@ -2272,7 +2259,7 @@ void StickerPanInner::updateSelected() { if (selIndex >= set.pack.size()) { selIndex = -1; } else { - if (set.id == RecentStickerSetId && _custom[selIndex]) { + if (set.id == Stickers::RecentSetId && _custom[selIndex]) { int32 inx = sx - (selIndex % StickerPanPerRow) * st::stickerPanSize.width(), iny = p.y() - y - ((selIndex / StickerPanPerRow) * st::stickerPanSize.height()); if (inx >= st::stickerPanSize.width() - st::stickerPanDelete.pxWidth() && iny < st::stickerPanDelete.pxHeight()) { selIndex += set.pack.size(); @@ -2287,11 +2274,11 @@ void StickerPanInner::updateSelected() { bool startanim = false; int oldSel = _selected, oldSelTab = oldSel / MatrixRowShift, xOldSel = -1, newSel = selIndex, newSelTab = newSel / MatrixRowShift, xNewSel = -1; - if (oldSel >= 0 && oldSelTab < _sets.size() && _sets[oldSelTab].id == RecentStickerSetId && oldSel >= oldSelTab * MatrixRowShift + _sets[oldSelTab].pack.size()) { + if (oldSel >= 0 && oldSelTab < _sets.size() && _sets[oldSelTab].id == Stickers::RecentSetId && oldSel >= oldSelTab * MatrixRowShift + _sets[oldSelTab].pack.size()) { xOldSel = oldSel; oldSel -= _sets[oldSelTab].pack.size(); } - if (newSel >= 0 && newSelTab < _sets.size() && _sets[newSelTab].id == RecentStickerSetId && newSel >= newSelTab * MatrixRowShift + _sets[newSelTab].pack.size()) { + if (newSel >= 0 && newSelTab < _sets.size() && _sets[newSelTab].id == Stickers::RecentSetId && newSel >= newSelTab * MatrixRowShift + _sets[newSelTab].pack.size()) { xNewSel = newSel; newSel -= _sets[newSelTab].pack.size(); } @@ -2346,9 +2333,11 @@ void StickerPanInner::onPreview() { if (_pressedSel < 0) return; if (_showingInlineItems) { int32 row = _pressedSel / MatrixRowShift, col = _pressedSel % MatrixRowShift; - if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size() && _inlineRows.at(row).items.at(col)->document() && _inlineRows.at(row).items.at(col)->document()->loaded()) { - Ui::showStickerPreview(_inlineRows.at(row).items.at(col)->document()); - _previewShown = true; + if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { + if (DocumentData *previewDocument = _inlineRows.at(row).items.at(col)->getPreviewDocument()) { + Ui::showStickerPreview(previewDocument); + _previewShown = true; + } } } else if (_pressedSel < MatrixRowShift * _sets.size()) { int tab = (_pressedSel / MatrixRowShift), sel = _pressedSel % MatrixRowShift; @@ -2391,7 +2380,7 @@ void StickerPanInner::step_selected(uint64 ms, bool timer) { void StickerPanInner::showStickerSet(uint64 setId) { clearSelection(true); - if (setId == NoneStickerSetId) { + if (setId == Stickers::NoneSetId) { bool wasNotShowingGifs = !_showingInlineItems; if (wasNotShowingGifs) { _showingInlineItems = true; @@ -2440,10 +2429,12 @@ void StickerPanInner::showStickerSet(uint64 setId) { void StickerPanInner::updateShowingSavedGifs() { if (cShowingSavedGifs()) { if (!_showingInlineItems) { + clearSelection(true); _showingSavedGifs = _showingInlineItems = true; if (_inlineRows.isEmpty()) refreshSavedGifs(); } } else if (!_showingInlineItems) { + clearSelection(true); _showingSavedGifs = false; } } @@ -2459,7 +2450,7 @@ EmojiPanel::EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool , _setId(setId) , _special(special) , _deleteVisible(false) -, _delete(special ? 0 : new IconedButton(this, st::notifyClose)) { // NoneStickerSetId if in emoji +, _delete(special ? 0 : new IconedButton(this, st::notifyClose)) { // Stickers::NoneSetId if in emoji resize(st::emojiPanWidth, st::emojiPanHeader); setMouseTracking(true); setFocusPolicy(Qt::NoFocus); @@ -2483,11 +2474,11 @@ void EmojiPanel::setText(const QString &text) { void EmojiPanel::updateText() { int32 availw = st::emojiPanWidth - st::emojiPanHeaderLeft * 2; if (_deleteVisible) { - if (!_special && _setId != NoneStickerSetId) { + if (!_special && _setId != Stickers::NoneSetId) { availw -= st::notifyClose.icon.pxWidth() + st::emojiPanHeaderLeft; } } else { - QString switchText = lang((_setId != NoneStickerSetId) ? lng_switch_emoji : (cSavedGifs().isEmpty() ? lng_switch_stickers : lng_switch_stickers_gifs)); + QString switchText = lang((_setId != Stickers::NoneSetId) ? lng_switch_emoji : (cSavedGifs().isEmpty() ? lng_switch_stickers : lng_switch_stickers_gifs)); availw -= st::emojiSwitchSkip + st::emojiPanHeaderFont->width(switchText); } _text = st::emojiPanHeaderFont->elided(_fullText, availw); @@ -2566,6 +2557,8 @@ void EmojiSwitchButton::paintEvent(QPaintEvent *e) { } } +} // namespace internal + EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) , _maxHeight(st::emojiPanMaxHeight) , _contentMaxHeight(st::emojiPanMaxHeight) @@ -2660,7 +2653,7 @@ EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) connect(&e_inner, SIGNAL(selected(EmojiPtr)), this, SIGNAL(emojiSelected(EmojiPtr))); connect(&s_inner, SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*))); connect(&s_inner, SIGNAL(selected(PhotoData*)), this, SIGNAL(photoSelected(PhotoData*))); - connect(&s_inner, SIGNAL(selected(InlineResult*,UserData*)), this, SIGNAL(inlineResultSelected(InlineResult*,UserData*))); + connect(&s_inner, SIGNAL(selected(InlineBots::Result*,UserData*)), this, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*))); connect(&s_inner, SIGNAL(emptyInlineRows()), this, SLOT(onEmptyInlineRows())); @@ -2780,7 +2773,7 @@ void EmojiPan::paintEvent(QPaintEvent *e) { if (!_icons.isEmpty()) { int32 x = _iconsLeft, i = 0, selxrel = _iconsLeft + _iconSelX.current(), selx = selxrel - _iconsX.current(); for (int32 l = _icons.size(); i < l && !_icons.at(i).sticker; ++i) { - bool gifs = (_icons.at(i).setId == NoneStickerSetId); + bool gifs = (_icons.at(i).setId == Stickers::NoneSetId); if (selxrel != x) { p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), gifs ? st::savedGifsOver : st::rbEmojiRecent.imageRect); } @@ -2800,7 +2793,7 @@ void EmojiPan::paintEvent(QPaintEvent *e) { i += _iconsX.current() / int(st::rbEmoji.width); x -= _iconsX.current() % int(st::rbEmoji.width); for (int32 l = qMin(_icons.size(), i + 8 - skip); i < l; ++i) { - const StickerIcon &s(_icons.at(i)); + const internal::StickerIcon &s(_icons.at(i)); s.sticker->thumb->load(); QPixmap pix(s.sticker->thumb->pix(s.pixw, s.pixh)); @@ -3345,13 +3338,13 @@ void EmojiPan::stickersInstalled(uint64 setId) { showStart(); } -void EmojiPan::ui_repaintInlineItem(const LayoutInlineItem *layout) { +void EmojiPan::ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { if (_stickersShown && !isHidden()) { s_inner.ui_repaintInlineItem(layout); } } -bool EmojiPan::ui_isInlineItemVisible(const LayoutInlineItem *layout) { +bool EmojiPan::ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { if (_stickersShown && !isHidden()) { return s_inner.ui_isInlineItemVisible(layout); } @@ -3366,9 +3359,9 @@ bool EmojiPan::ui_isInlineItemBeingChosen() { } void EmojiPan::notify_automaticLoadSettingsChangedGif() { - for (InlineCache::const_iterator i = _inlineCache.cbegin(), ei = _inlineCache.cend(); i != ei; ++i) { - for (InlineResults::const_iterator j = i.value()->results.cbegin(), ej = i.value()->results.cend(); j != ej; ++j) { - (*j)->automaticLoadSettingsChangedGif(); + for_const (const InlineCacheEntry *entry, _inlineCache) { + for_const (InlineBots::Result *l, entry->results) { + l->automaticLoadSettingsChangedGif(); } } } @@ -3427,7 +3420,7 @@ void EmojiPan::onTabChange() { e_inner.showEmojiPack(newTab); } -void EmojiPan::updatePanelsPositions(const QVector &panels, int32 st) { +void EmojiPan::updatePanelsPositions(const QVector &panels, int32 st) { for (int32 i = 0, l = panels.size(); i < l; ++i) { int32 y = panels.at(i)->wantedY() - st; if (y < 0) { @@ -3511,9 +3504,9 @@ void EmojiPan::onSwitch() { Notify::clipStopperHidden(ClipStopperSavedGifsPanel); } else { if (cShowingSavedGifs() && cSavedGifs().isEmpty()) { - s_inner.showStickerSet(DefaultStickerSetId); - } else if (!cShowingSavedGifs() && !cSavedGifs().isEmpty() && cStickerSets().isEmpty()) { - s_inner.showStickerSet(NoneStickerSetId); + s_inner.showStickerSet(Stickers::DefaultSetId); + } else if (!cShowingSavedGifs() && !cSavedGifs().isEmpty() && Global::StickerSets().isEmpty()) { + s_inner.showStickerSet(Stickers::NoneSetId); } else { s_inner.updateShowingSavedGifs(); } @@ -3551,8 +3544,8 @@ void EmojiPan::onSwitch() { } void EmojiPan::onRemoveSet(quint64 setId) { - StickerSets::const_iterator it = cStickerSets().constFind(setId); - if (it != cStickerSets().cend() && !(it->flags & MTPDstickerSet::flag_official)) { + auto it = Global::StickerSets().constFind(setId); + if (it != Global::StickerSets().cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) { _removingSetId = it->id; ConfirmBox *box = new ConfirmBox(lng_stickers_remove_pack(lt_sticker_pack, it->title), lang(lng_box_remove)); connect(box, SIGNAL(confirmed()), this, SLOT(onRemoveSetSure())); @@ -3563,8 +3556,8 @@ void EmojiPan::onRemoveSet(quint64 setId) { void EmojiPan::onRemoveSetSure() { Ui::hideLayer(); - StickerSets::iterator it = cRefStickerSets().find(_removingSetId); - if (it != cRefStickerSets().cend() && !(it->flags & MTPDstickerSet::flag_official)) { + auto it = Global::RefStickerSets().find(_removingSetId); + if (it != Global::RefStickerSets().cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) { if (it->id && it->access) { MTP::send(MTPmessages_UninstallStickerSet(MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)))); } else if (!it->shortName.isEmpty()) { @@ -3580,9 +3573,9 @@ void EmojiPan::onRemoveSetSure() { ++i; } } - cRefStickerSets().erase(it); - int32 removeIndex = cStickerSetsOrder().indexOf(_removingSetId); - if (removeIndex >= 0) cRefStickerSetsOrder().removeAt(removeIndex); + Global::RefStickerSets().erase(it); + int removeIndex = Global::StickerSetsOrder().indexOf(_removingSetId); + if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); refreshStickers(); Local::writeStickers(); if (writeRecent) Local::writeUserSettings(); @@ -3607,6 +3600,13 @@ bool EmojiPan::hideOnNoInlineResults() { return _inlineBot && _stickersShown && s_inner.inlineResultsShown() && (_shownFromInlineQuery || _inlineBot->username != cInlineGifBotUsername()); } +void EmojiPan::InlineCacheEntry::clearResults() { + for_const (const InlineBots::Result *result, results) { + delete result; + } + results.clear(); +} + void EmojiPan::inlineBotChanged() { if (!_inlineBot) return; @@ -3638,7 +3638,7 @@ void EmojiPan::inlineResultsDone(const MTPmessages_BotResults &result) { bool adding = (it != _inlineCache.cend()); if (result.type() == mtpc_messages_botResults) { - const MTPDmessages_botResults &d(result.c_messages_botResults()); + const auto &d(result.c_messages_botResults()); const QVector &v(d.vresults.c_vector().v); uint64 queryId(d.vquery_id.v); @@ -3646,78 +3646,20 @@ void EmojiPan::inlineResultsDone(const MTPmessages_BotResults &result) { it = _inlineCache.insert(_inlineQuery, new InlineCacheEntry()); } it.value()->nextOffset = qs(d.vnext_offset); + if (d.has_switch_pm() && d.vswitch_pm.type() == mtpc_inlineBotSwitchPM) { + const auto &switchPm = d.vswitch_pm.c_inlineBotSwitchPM(); + it.value()->switchPmText = qs(switchPm.vtext); + it.value()->switchPmStartParam = qs(switchPm.vstart_param); + } - int32 count = v.size(), added = 0; - if (count) { + if (int count = v.size()) { it.value()->results.reserve(it.value()->results.size() + count); } - for (int32 i = 0; i < count; ++i) { - InlineResult *result = new InlineResult(queryId); - const MTPBotInlineMessage *message = 0; - switch (v.at(i).type()) { - case mtpc_botInlineMediaResultPhoto: { - const MTPDbotInlineMediaResultPhoto &r(v.at(i).c_botInlineMediaResultPhoto()); - result->id = qs(r.vid); - result->type = qs(r.vtype); - result->photo = App::feedPhoto(r.vphoto); - message = &r.vsend_message; - } break; - case mtpc_botInlineMediaResultDocument: { - const MTPDbotInlineMediaResultDocument &r(v.at(i).c_botInlineMediaResultDocument()); - result->id = qs(r.vid); - result->type = qs(r.vtype); - result->doc = App::feedDocument(r.vdocument); - message = &r.vsend_message; - } break; - case mtpc_botInlineResult: { - const MTPDbotInlineResult &r(v.at(i).c_botInlineResult()); - result->id = qs(r.vid); - result->type = qs(r.vtype); - result->title = r.has_title() ? qs(r.vtitle) : QString(); - result->description = r.has_description() ? qs(r.vdescription) : QString(); - result->url = r.has_url() ? qs(r.vurl) : QString(); - result->thumb_url = r.has_thumb_url() ? qs(r.vthumb_url) : QString(); - result->content_type = r.has_content_type() ? qs(r.vcontent_type) : QString(); - result->content_url = r.has_content_url() ? qs(r.vcontent_url) : QString(); - result->width = r.has_w() ? r.vw.v : 0; - result->height = r.has_h() ? r.vh.v : 0; - result->duration = r.has_duration() ? r.vduration.v : 0; - if (!result->thumb_url.isEmpty() && (result->thumb_url.startsWith(qstr("http://"), Qt::CaseInsensitive) || result->thumb_url.startsWith(qstr("https://"), Qt::CaseInsensitive))) { - result->thumb = ImagePtr(result->thumb_url); - } - message = &r.vsend_message; - } break; - } - bool badAttachment = (result->photo && !result->photo->access) || (result->doc && !result->doc->access); - - if (!message) { - delete result; - continue; - } - switch (message->type()) { - case mtpc_botInlineMessageMediaAuto: { - const MTPDbotInlineMessageMediaAuto &r(message->c_botInlineMessageMediaAuto()); - result->caption = qs(r.vcaption); - } break; - - case mtpc_botInlineMessageText: { - const MTPDbotInlineMessageText &r(message->c_botInlineMessageText()); - result->message = qs(r.vmessage); - if (r.has_entities()) result->entities = entitiesFromMTP(r.ventities.c_vector().v); - result->noWebPage = r.is_no_webpage(); - } break; - - default: { - badAttachment = true; - } break; - } - - bool canSend = (result->photo || result->doc || !result->message.isEmpty() || (!result->content_url.isEmpty() && (result->type == qstr("gif") || result->type == qstr("photo")))); - if (result->type.isEmpty() || badAttachment || !canSend) { - delete result; - } else { + int added = 0; + for_const (const MTPBotInlineResult &res, v) { + if (UniquePointer result = InlineBots::Result::create(queryId, res)) { ++added; - it.value()->results.push_back(result); + it.value()->results.push_back(result.release()); } } @@ -3748,7 +3690,14 @@ void EmojiPan::queryInlineBot(UserData *bot, QString query) { inlineBotChanged(); _inlineBot = bot; force = true; + if (_inlineBot->isBotInlineGeo()) { + Ui::showLayer(new InformBox(lang(lng_bot_inline_geo_unavailable))); + } } + if (_inlineBot && _inlineBot->isBotInlineGeo()) { + return; + } + if (_inlineQuery != query || force) { if (_inlineRequestId) { MTP::cancel(_inlineRequestId); @@ -3777,7 +3726,8 @@ void EmojiPan::onInlineRequest() { if (nextOffset.isEmpty()) return; } Notify::inlineBotRequesting(true); - _inlineRequestId = MTP::send(MTPmessages_GetInlineBotResults(_inlineBot->inputUser, MTP_string(_inlineQuery), MTP_string(nextOffset)), rpcDone(&EmojiPan::inlineResultsDone), rpcFail(&EmojiPan::inlineResultsFail)); + MTPmessages_GetInlineBotResults::Flags flags = 0; + _inlineRequestId = MTP::send(MTPmessages_GetInlineBotResults(MTP_flags(flags), _inlineBot->inputUser, MTPInputGeoPoint(), MTP_string(_inlineQuery), MTP_string(nextOffset)), rpcDone(&EmojiPan::inlineResultsDone), rpcFail(&EmojiPan::inlineResultsFail)); } void EmojiPan::onEmptyInlineRows() { @@ -3798,7 +3748,7 @@ bool EmojiPan::refreshInlineRows(int32 *added) { _inlineNextOffset = i.value()->nextOffset; } if (clear) prepareShowHideCache(); - int32 result = s_inner.refreshInlineRows(_inlineBot, clear ? InlineResults() : i.value()->results, false); + int32 result = s_inner.refreshInlineRows(_inlineBot, clear ? internal::InlineResults() : i.value()->results, false); if (added) *added = result; return !clear; } @@ -3842,133 +3792,187 @@ void EmojiPan::recountContentMaxHeight() { updateContentHeight(); } -MentionsInner::MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows) +MentionsInner::MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows) : _parent(parent) , _mrows(mrows) , _hrows(hrows) , _brows(brows) +, _srows(srows) +, _stickersPerRow(1) +, _recentInlineBotsInRows(0) , _sel(-1) +, _down(-1) , _mouseSel(false) -, _overDelete(false) { +, _overDelete(false) +, _previewShown(false) { + _previewTimer.setSingleShot(true); + connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); } void MentionsInner::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); + + QRect r(e->rect()); + if (r != rect()) p.setClipRect(r); int32 atwidth = st::mentionFont->width('@'), hashwidth = st::mentionFont->width('#'); int32 mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize; int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right(); int32 htagleft = st::btnAttachPhoto.width + st::taMsgField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width; - int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1; - int32 last = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size(); - bool hasUsername = _parent->filter().indexOf('@') > 1; - for (int32 i = from; i < to; ++i) { - if (i >= last) break; + if (!_srows->isEmpty()) { + int32 rows = rowscount(_srows->size(), _stickersPerRow); + int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows); + int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows); + int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow); + int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow); + for (int32 row = fromrow; row < torow; ++row) { + for (int32 col = fromcol; col < tocol; ++col) { + int32 index = row * _stickersPerRow + col; + if (index >= _srows->size()) break; - bool selected = (i == _sel); - if (selected) { - p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver->b); - int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2; - if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) p.drawPixmap(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), App::sprite(), st::notifyClose.icon); + DocumentData *sticker = _srows->at(index); + if (!sticker->sticker()) continue; + + QPoint pos(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height()); + if (_sel == index) { + QPoint tl(pos); + if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width()); + App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners); + } + + bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); + if (goodThumb) { + sticker->thumb->load(); + } else { + sticker->checkSticker(); + } + + float64 coef = qMin((st::stickerPanSize.width() - st::msgRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::msgRadius * 2) / float64(sticker->dimensions.height())); + if (coef > 1) coef = 1; + int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height()); + if (w < 1) w = 1; + if (h < 1) h = 1; + QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); + if (goodThumb) { + p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h)); + } else if (!sticker->sticker()->img->isNull()) { + p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h)); + } + } } - p.setPen(st::black->p); - if (!_mrows->isEmpty()) { - UserData *user = _mrows->at(i); - QString first = (_parent->filter().size() < 2) ? QString() : ('@' + user->username.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('@' + user->username) : user->username.mid(_parent->filter().size() - 1); - int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second), unamewidth = firstwidth + secondwidth, namewidth = user->nameText.maxWidth(); - if (mentionwidth < unamewidth + namewidth) { - namewidth = (mentionwidth * namewidth) / (namewidth + unamewidth); - unamewidth = mentionwidth - namewidth; - if (firstwidth < unamewidth + st::mentionFont->elidew) { - if (firstwidth < unamewidth) { - first = st::mentionFont->elided(first, unamewidth); - } else if (!second.isEmpty()) { - first = st::mentionFont->elided(first + second, unamewidth); - second = QString(); + } else { + int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1; + int32 last = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size(); + bool hasUsername = _parent->filter().indexOf('@') > 1; + for (int32 i = from; i < to; ++i) { + if (i >= last) break; + + bool selected = (i == _sel); + if (selected) { + p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver->b); + int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2; + if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) p.drawPixmap(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), App::sprite(), st::notifyClose.icon); + } + p.setPen(st::black->p); + if (!_mrows->isEmpty()) { + UserData *user = _mrows->at(i); + QString first = (_parent->filter().size() < 2) ? QString() : ('@' + user->username.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('@' + user->username) : user->username.mid(_parent->filter().size() - 1); + int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second), unamewidth = firstwidth + secondwidth, namewidth = user->nameText.maxWidth(); + if (mentionwidth < unamewidth + namewidth) { + namewidth = (mentionwidth * namewidth) / (namewidth + unamewidth); + unamewidth = mentionwidth - namewidth; + if (firstwidth < unamewidth + st::mentionFont->elidew) { + if (firstwidth < unamewidth) { + first = st::mentionFont->elided(first, unamewidth); + } else if (!second.isEmpty()) { + first = st::mentionFont->elided(first + second, unamewidth); + second = QString(); + } + } else { + second = st::mentionFont->elided(second, unamewidth - firstwidth); } - } else { - second = st::mentionFont->elided(second, unamewidth - firstwidth); } - } - user->photo->load(); - p.drawPixmap(st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), user->photo->pixRounded(st::mentionPhotoSize)); - user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth); + user->loadUserpic(); + user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width()); + user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth); - p.setFont(st::mentionFont->f); - p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); - p.drawText(mentionleft + namewidth + st::mentionPadding.right(), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); - if (!second.isEmpty()) { - p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); - p.drawText(mentionleft + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); - } - } else if (!_hrows->isEmpty()) { - QString hrow = _hrows->at(i); - QString first = (_parent->filter().size() < 2) ? QString() : ('#' + hrow.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('#' + hrow) : hrow.mid(_parent->filter().size() - 1); - int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second); - if (htagwidth < firstwidth + secondwidth) { - if (htagwidth < firstwidth + st::mentionFont->elidew) { - first = st::mentionFont->elided(first + second, htagwidth); - second = QString(); - } else { - second = st::mentionFont->elided(second, htagwidth - firstwidth); - } - } - - p.setFont(st::mentionFont->f); - if (!first.isEmpty()) { + p.setFont(st::mentionFont->f); p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); - p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); - } - if (!second.isEmpty()) { - p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); - p.drawText(htagleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); - } - } else { - UserData *user = _brows->at(i).first; - - const BotCommand *command = _brows->at(i).second; - QString toHighlight = command->command; - int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1); - if (hasUsername || botStatus == 0 || botStatus == 2) { - toHighlight += '@' + user->username; - } - if (true || _parent->chat() || botStatus == 0 || botStatus == 2) { - user->photo->load(); - p.drawPixmap(st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), user->photo->pixRounded(st::mentionPhotoSize)); - } - - int32 addleft = 0, widthleft = mentionwidth; - QString first = (_parent->filter().size() < 2) ? QString() : ('/' + toHighlight.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('/' + toHighlight) : toHighlight.mid(_parent->filter().size() - 1); - int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second); - if (widthleft < firstwidth + secondwidth) { - if (widthleft < firstwidth + st::mentionFont->elidew) { - first = st::mentionFont->elided(first + second, widthleft); - second = QString(); - } else { - second = st::mentionFont->elided(second, widthleft - firstwidth); + p.drawText(mentionleft + namewidth + st::mentionPadding.right(), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); + if (!second.isEmpty()) { + p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); + p.drawText(mentionleft + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); + } + } else if (!_hrows->isEmpty()) { + QString hrow = _hrows->at(i); + QString first = (_parent->filter().size() < 2) ? QString() : ('#' + hrow.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('#' + hrow) : hrow.mid(_parent->filter().size() - 1); + int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second); + if (htagwidth < firstwidth + secondwidth) { + if (htagwidth < firstwidth + st::mentionFont->elidew) { + first = st::mentionFont->elided(first + second, htagwidth); + second = QString(); + } else { + second = st::mentionFont->elided(second, htagwidth - firstwidth); + } + } + + p.setFont(st::mentionFont->f); + if (!first.isEmpty()) { + p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); + p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); + } + if (!second.isEmpty()) { + p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); + p.drawText(htagleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); + } + } else { + UserData *user = _brows->at(i).first; + + const BotCommand *command = _brows->at(i).second; + QString toHighlight = command->command; + int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1); + if (hasUsername || botStatus == 0 || botStatus == 2) { + toHighlight += '@' + user->username; + } + user->loadUserpic(); + user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width()); + + int32 addleft = 0, widthleft = mentionwidth; + QString first = (_parent->filter().size() < 2) ? QString() : ('/' + toHighlight.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('/' + toHighlight) : toHighlight.mid(_parent->filter().size() - 1); + int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second); + if (widthleft < firstwidth + secondwidth) { + if (widthleft < firstwidth + st::mentionFont->elidew) { + first = st::mentionFont->elided(first + second, widthleft); + second = QString(); + } else { + second = st::mentionFont->elided(second, widthleft - firstwidth); + } + } + p.setFont(st::mentionFont->f); + if (!first.isEmpty()) { + p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); + p.drawText(mentionleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); + } + if (!second.isEmpty()) { + p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); + p.drawText(mentionleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); + } + addleft += firstwidth + secondwidth + st::mentionPadding.left(); + widthleft -= firstwidth + secondwidth + st::mentionPadding.left(); + if (widthleft > st::mentionFont->elidew && !command->descriptionText().isEmpty()) { + p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); + command->descriptionText().drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft, 1, style::al_right); } - } - p.setFont(st::mentionFont->f); - if (!first.isEmpty()) { - p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); - p.drawText(mentionleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); - } - if (!second.isEmpty()) { - p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); - p.drawText(mentionleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); - } - addleft += firstwidth + secondwidth + st::mentionPadding.left(); - widthleft -= firstwidth + secondwidth + st::mentionPadding.left(); - if (widthleft > st::mentionFont->elidew && !command->descriptionText().isEmpty()) { - p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); - command->descriptionText().drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft, 1, style::al_right); } } + p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerBottom() - st::lineWidth, width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b); } + p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b); +} - p.fillRect(cWideMode() ? st::lineWidth : 0, _parent->innerTop(), width() - (cWideMode() ? st::lineWidth : 0), st::lineWidth, st::shadowColor->b); - p.fillRect(cWideMode() ? st::lineWidth : 0, _parent->innerBottom() - st::lineWidth, width() - (cWideMode() ? st::lineWidth : 0), st::lineWidth, st::shadowColor->b); +void MentionsInner::resizeEvent(QResizeEvent *e) { + _stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width())); } void MentionsInner::mouseMoveEvent(QMouseEvent *e) { @@ -3977,31 +3981,54 @@ void MentionsInner::mouseMoveEvent(QMouseEvent *e) { onUpdateSelected(true); } -void MentionsInner::clearSel() { +void MentionsInner::clearSel(bool hidden) { _mouseSel = _overDelete = false; setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty()) ? -1 : 0); + if (hidden) { + _down = -1; + _previewShown = false; + } } -bool MentionsInner::moveSel(int direction) { +bool MentionsInner::moveSel(int key) { _mouseSel = false; - int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size()); + int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size()); + int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0); + if (!_srows->isEmpty()) { + if (key == Qt::Key_Left) { + direction = -1; + } else if (key == Qt::Key_Right) { + direction = 1; + } else { + direction *= _stickersPerRow; + } + } if (_sel >= maxSel || _sel < 0) { - if (direction < 0) { + if (direction < -1) { + setSel(((maxSel - 1) / _stickersPerRow) * _stickersPerRow, true); + } else if (direction < 0) { setSel(maxSel - 1, true); } else { setSel(0, true); } return (_sel >= 0 && _sel < maxSel); } - setSel((_sel + direction >= maxSel) ? -1 : (_sel + direction), true); + setSel((_sel + direction >= maxSel || _sel + direction < 0) ? -1 : (_sel + direction), true); return true; } bool MentionsInner::select() { - QString sel = getSelected(); - if (!sel.isEmpty()) { - emit chosen(sel); - return true; + if (!_srows->isEmpty()) { + if (_sel >= 0 && _sel < _srows->size()) { + emit selected(_srows->at(_sel)); + return true; + } + } else { + QString sel = getSelected(); + if (!sel.isEmpty()) { + emit chosen(sel); + return true; + } } return false; } @@ -4068,12 +4095,35 @@ void MentionsInner::mousePressEvent(QMouseEvent *e) { _mouseSel = true; onUpdateSelected(true); - } else { + } else if (_srows->isEmpty()) { select(); + } else { + _down = _sel; + _previewTimer.start(QApplication::startDragTime()); } } } +void MentionsInner::mouseReleaseEvent(QMouseEvent *e) { + _previewTimer.stop(); + + int32 pressed = _down; + _down = -1; + + _mousePos = mapToGlobal(e->pos()); + _mouseSel = true; + onUpdateSelected(true); + + if (_previewShown) { + _previewShown = false; + return; + } + + if (_sel < 0 || _sel != pressed || _srows->isEmpty()) return; + + select(); +} + void MentionsInner::enterEvent(QEvent *e) { setMouseTracking(true); _mousePos = QCursor::pos(); @@ -4087,26 +4137,64 @@ void MentionsInner::leaveEvent(QEvent *e) { } } +void MentionsInner::updateSelectedRow() { + if (_sel >= 0) { + if (_srows->isEmpty()) { + update(0, _sel * st::mentionHeight, width(), st::mentionHeight); + } else { + int32 row = _sel / _stickersPerRow, col = _sel % _stickersPerRow; + update(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanSize.width(), st::stickerPanSize.height()); + } + } +} + void MentionsInner::setSel(int sel, bool scroll) { - if (_sel >= 0) update(0, _sel * st::mentionHeight, width(), st::mentionHeight); + updateSelectedRow(); _sel = sel; - if (_sel >= 0) update(0, _sel * st::mentionHeight, width(), st::mentionHeight); - int32 maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size(); - if (scroll && _sel >= 0 && _sel < maxSel) emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight); + updateSelectedRow(); + + if (scroll && _sel >= 0) { + if (_srows->isEmpty()) { + emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight); + } else { + int32 row = _sel / _stickersPerRow; + emit mustScrollTo(st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanPadding + (row + 1) * st::stickerPanSize.height()); + } + } } void MentionsInner::onUpdateSelected(bool force) { QPoint mouse(mapFromGlobal(_mousePos)); if ((!force && !rect().contains(mouse)) || !_mouseSel) return; - int w = width(), mouseY = mouse.y(); - int32 sel = mouseY / int32(st::mentionHeight), maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size(); - _overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= w - st::mentionHeight) : false; + if (_down >= 0 && !_previewShown) return; + + int32 sel = -1, maxSel = 0; + if (!_srows->isEmpty()) { + int32 rows = rowscount(_srows->size(), _stickersPerRow); + int32 row = (mouse.y() >= st::stickerPanPadding) ? ((mouse.y() - st::stickerPanPadding) / st::stickerPanSize.height()) : -1; + int32 col = (mouse.x() >= st::stickerPanPadding) ? ((mouse.x() - st::stickerPanPadding) / st::stickerPanSize.width()) : -1; + if (row >= 0 && col >= 0) { + sel = row * _stickersPerRow + col; + } + maxSel = _srows->size(); + _overDelete = false; + } else { + sel = mouse.y() / int32(st::mentionHeight); + maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size(); + _overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false; + } if (sel < 0 || sel >= maxSel) { sel = -1; } if (sel != _sel) { setSel(sel); + if (_down >= 0 && _sel >= 0 && _down != _sel) { + _down = _sel; + if (_down >= 0 && _down < _srows->size()) { + Ui::showStickerPreview(_srows->at(_down)); + } + } } } @@ -4118,9 +4206,16 @@ void MentionsInner::onParentGeometryChanged() { } } +void MentionsInner::onPreview() { + if (_down >= 0 && _down < _srows->size()) { + Ui::showStickerPreview(_srows->at(_down)); + _previewShown = true; + } +} + MentionsDropdown::MentionsDropdown(QWidget *parent) : TWidget(parent) , _scroll(this, st::mentionScroll) -, _inner(this, &_mrows, &_hrows, &_brows) +, _inner(this, &_mrows, &_hrows, &_brows, &_srows) , _chat(0) , _user(0) , _channel(0) @@ -4131,6 +4226,7 @@ MentionsDropdown::MentionsDropdown(QWidget *parent) : TWidget(parent) _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); connect(&_inner, SIGNAL(chosen(QString)), this, SIGNAL(chosen(QString))); + connect(&_inner, SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*))); connect(&_inner, SIGNAL(mustScrollTo(int,int)), &_scroll, SLOT(scrollToY(int,int))); connect(App::wnd(), SIGNAL(imageLoaded()), &_inner, SLOT(update())); @@ -4151,7 +4247,7 @@ MentionsDropdown::MentionsDropdown(QWidget *parent) : TWidget(parent) } void MentionsDropdown::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); if (_a_appearance.animating()) { p.setOpacity(a_opacity.current()); @@ -4159,29 +4255,43 @@ void MentionsDropdown::paintEvent(QPaintEvent *e) { return; } - p.fillRect(rect(), st::white->b); - + p.fillRect(rect(), st::white); } void MentionsDropdown::showFiltered(PeerData *peer, QString query, bool start) { - if (query.isEmpty() || (peer->isUser() && query.at(0) == '@' && (!start || cRecentInlineBots().isEmpty()))) { - if (!isHidden()) { - hideStart(); - } - return; - } - _chat = peer->asChat(); _user = peer->asUser(); _channel = peer->asChannel(); + if (query.isEmpty()) { + rowsUpdated(MentionRows(), HashtagRows(), BotCommandRows(), _srows, false); + return; + } + + _emoji = EmojiPtr(); + query = query.toLower(); - bool toDown = (_filter != query); - if (toDown) { + bool resetScroll = (_filter != query); + if (resetScroll) { _filter = query; } _addInlineBots = start; - updateFiltered(toDown); + updateFiltered(resetScroll); +} + +void MentionsDropdown::showStickers(EmojiPtr emoji) { + bool resetScroll = (_emoji != emoji); + _emoji = emoji; + if (!emoji) { + rowsUpdated(_mrows, _hrows, _brows, StickerPack(), false); + return; + } + + _chat = 0; + _user = 0; + _channel = 0; + + updateFiltered(resetScroll); } bool MentionsDropdown::clearFilteredBotCommands() { @@ -4190,12 +4300,49 @@ bool MentionsDropdown::clearFilteredBotCommands() { return true; } -void MentionsDropdown::updateFiltered(bool toDown) { +namespace { + template + inline int indexOfInFirstN(const T &v, const U &elem, int last) { + for (auto b = v.cbegin(), i = b, e = b + qMax(v.size(), last); i != e; ++i) { + if (*i == elem) { + return (i - b); + } + } + return -1; + } +} + +void MentionsDropdown::updateFiltered(bool resetScroll) { int32 now = unixtime(), recentInlineBots = 0; MentionRows mrows; HashtagRows hrows; BotCommandRows brows; - if (_filter.at(0) == '@') { + StickerPack srows; + if (_emoji) { + QMap setsToRequest; + Stickers::Sets &sets(Global::RefStickerSets()); + const Stickers::Order &order(Global::StickerSetsOrder()); + for (int i = 0, l = order.size(); i < l; ++i) { + auto it = sets.find(order.at(i)); + if (it != sets.cend()) { + if (it->emoji.isEmpty()) { + setsToRequest.insert(it->id, it->access); + it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; + } else if (!(it->flags & MTPDstickerSet::Flag::f_disabled)) { + StickersByEmojiMap::const_iterator i = it->emoji.constFind(emojiGetNoColor(_emoji)); + if (i != it->emoji.cend()) { + srows += *i; + } + } + } + } + if (!setsToRequest.isEmpty() && App::api()) { + for (QMap::const_iterator i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) { + App::api()->scheduleStickerSetRequest(i.key(), i.value()); + } + App::api()->requestStickerSets(); + } + } else if (_filter.at(0) == '@') { if (_chat) { mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size())); } else if (_channel && _channel->isMegagroup()) { @@ -4215,46 +4362,49 @@ void MentionsDropdown::updateFiltered(bool toDown) { ++recentInlineBots; } } - } - if (_filter.at(0) == '@' && _chat) { - QMultiMap ordered; - mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size())); - if (_chat->noParticipantInfo()) { - if (App::api()) App::api()->requestFullPeer(_chat); - } else if (!_chat->participants.isEmpty()) { - for (ChatData::Participants::const_iterator i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) { - UserData *user = i.key(); - if (user->username.isEmpty()) continue; - if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; - ordered.insertMulti(App::onlineForSort(user, now), user); + if (_chat) { + QMultiMap ordered; + mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size())); + if (_chat->noParticipantInfo()) { + if (App::api()) App::api()->requestFullPeer(_chat); + } else if (!_chat->participants.isEmpty()) { + for (ChatData::Participants::const_iterator i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) { + UserData *user = i.key(); + if (user->username.isEmpty()) continue; + if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; + if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue; + ordered.insertMulti(App::onlineForSort(user, now), user); + } } - } - for (MentionRows::const_iterator i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) { - UserData *user = *i; - if (user->username.isEmpty()) continue; - if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; - mrows.push_back(user); - if (!ordered.isEmpty()) { - ordered.remove(App::onlineForSort(user, now), user); - } - } - if (!ordered.isEmpty()) { - for (QMultiMap::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) { - --i; - mrows.push_back(i.value()); - } - } - } else if (_filter.at(0) == '@' && _channel && _channel->isMegagroup()) { - QMultiMap ordered; - if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) { - if (App::api()) App::api()->requestLastParticipants(_channel); - } else { - mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size()); - for (MegagroupInfo::LastParticipants::const_iterator i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) { + for (MentionRows::const_iterator i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) { UserData *user = *i; if (user->username.isEmpty()) continue; if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; + if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue; mrows.push_back(user); + if (!ordered.isEmpty()) { + ordered.remove(App::onlineForSort(user, now), user); + } + } + if (!ordered.isEmpty()) { + for (QMultiMap::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) { + --i; + mrows.push_back(i.value()); + } + } + } else if (_channel && _channel->isMegagroup()) { + QMultiMap ordered; + if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) { + if (App::api()) App::api()->requestLastParticipants(_channel); + } else { + mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size()); + for (MegagroupInfo::LastParticipants::const_iterator i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) { + UserData *user = *i; + if (user->username.isEmpty()) continue; + if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; + if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue; + mrows.push_back(user); + } } } } else if (_filter.at(0) == '#') { @@ -4289,8 +4439,7 @@ void MentionsDropdown::updateFiltered(bool toDown) { if (_channel->mgInfo->bots.isEmpty()) { if (!_channel->mgInfo->botStatus && App::api()) App::api()->requestBots(_channel); } else { - for (MegagroupInfo::Bots::const_iterator i = _channel->mgInfo->bots.cbegin(), e = _channel->mgInfo->bots.cend(); i != e; ++i) { - UserData *user = i.key(); + for_const (auto *user, _channel->mgInfo->bots) { if (!user->botInfo) continue; if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user); if (user->botInfo->commands.isEmpty()) continue; @@ -4333,28 +4482,32 @@ void MentionsDropdown::updateFiltered(bool toDown) { } } } - rowsUpdated(mrows, hrows, brows, toDown); + rowsUpdated(mrows, hrows, brows, srows, resetScroll); _inner.setRecentInlineBotsInRows(recentInlineBots); } -void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown) { - if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty()) { +void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, const StickerPack &srows, bool resetScroll) { + if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.isEmpty()) { if (!isHidden()) { hideStart(); } _mrows.clear(); _hrows.clear(); _brows.clear(); + _srows.clear(); } else { _mrows = mrows; _hrows = hrows; _brows = brows; + _srows = srows; + bool hidden = _hiding || isHidden(); if (hidden) { show(); _scroll.show(); } - recount(toDown); + recount(resetScroll); + update(); if (hidden) { hide(); showStart(); @@ -4364,31 +4517,37 @@ void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows & void MentionsDropdown::setBoundings(QRect boundings) { _boundings = boundings; - resize(_boundings.width(), height()); - _scroll.resize(size()); - _inner.resize(width(), _inner.height()); recount(); } -void MentionsDropdown::recount(bool toDown) { - int32 h = (_mrows.isEmpty() ? (_hrows.isEmpty() ? _brows.size() : _hrows.size()) : _mrows.size()) * st::mentionHeight, oldst = _scroll.scrollTop(), st = oldst; +void MentionsDropdown::recount(bool resetScroll) { + int32 h = 0, oldst = _scroll.scrollTop(), st = oldst, maxh = 4.5 * st::mentionHeight; + if (!_srows.isEmpty()) { + int32 stickersPerRow = qMax(1, int32(_boundings.width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width())); + int32 rows = rowscount(_srows.size(), stickersPerRow); + h = st::stickerPanPadding + rows * st::stickerPanSize.height(); + } else if (!_mrows.isEmpty()) { + h = _mrows.size() * st::mentionHeight; + } else if (!_hrows.isEmpty()) { + h = _hrows.size() * st::mentionHeight; + } else if (!_brows.isEmpty()) { + h = _brows.size() * st::mentionHeight; + } - if (_inner.height() != h) { -// st += h - _inner.height(); - _inner.resize(width(), h); + if (_inner.width() != _boundings.width() || _inner.height() != h) { + _inner.resize(_boundings.width(), h); } if (h > _boundings.height()) h = _boundings.height(); - if (h > 4.5 * st::mentionHeight) h = 4.5 * st::mentionHeight; - if (height() != h) { -// st += _scroll.height() - h; - setGeometry(0, _boundings.height() - h, width(), h); - _scroll.resize(width(), h); - } else if (y() != _boundings.height() - h) { - move(0, _boundings.height() - h); + if (h > maxh) h = maxh; + if (width() != _boundings.width() || height() != h) { + setGeometry(_boundings.x(), _boundings.y() + _boundings.height() - h, _boundings.width(), h); + _scroll.resize(_boundings.width(), h); + } else if (y() != _boundings.y() + _boundings.height() - h) { + move(_boundings.x(), _boundings.y() + _boundings.height() - h); } - if (toDown) st = 0;// _scroll.scrollTopMax(); + if (resetScroll) st = 0; if (st != oldst) _scroll.scrollToY(st); - if (toDown) _inner.clearSel(); + if (resetScroll) _inner.clearSel(); } void MentionsDropdown::fastHide() { @@ -4418,7 +4577,7 @@ void MentionsDropdown::hideFinish() { hide(); _hiding = false; _filter = qsl("-"); - _inner.clearSel(); + _inner.clearSel(true); } void MentionsDropdown::showStart() { @@ -4488,13 +4647,12 @@ bool MentionsDropdown::eventFilter(QObject *obj, QEvent *e) { if (isHidden()) return QWidget::eventFilter(obj, e); if (e->type() == QEvent::KeyPress) { QKeyEvent *ev = static_cast(e); - if (ev->key() == Qt::Key_Up) { - _inner.moveSel(-1); - return true; - } else if (ev->key() == Qt::Key_Down) { - return _inner.moveSel(1); - } else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) { - return _inner.select(); + if (!(ev->modifiers() & (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier | Qt::MetaModifier))) { + if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down || (!_srows.isEmpty() && (ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right))) { + return _inner.moveSel(ev->key()); + } else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) { + return _inner.select(); + } } } return QWidget::eventFilter(obj, e); diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index b0cac7d405..5089967cfa 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -157,8 +157,21 @@ private: }; -class EmojiPanel; -static const int EmojiColorsCount = 5; +namespace InlineBots { +namespace Layout { +class ItemBase; +} // namespace Layout +class Result; +} // namespace InlineBots + +namespace internal { + +constexpr int InlineItemsMaxPerRow = 5; +constexpr int EmojiColorsCount = 5; + +using InlineResult = InlineBots::Result; +using InlineResults = QList; +using InlineItem = InlineBots::Layout::ItemBase; class EmojiColorPicker : public TWidget { Q_OBJECT @@ -222,6 +235,7 @@ private: }; +class EmojiPanel; class EmojiPanInner : public TWidget { Q_OBJECT @@ -359,8 +373,8 @@ public: uint64 currentSet(int yOffset) const; - void ui_repaintInlineItem(const LayoutInlineItem *layout); - bool ui_isInlineItemVisible(const LayoutInlineItem *layout); + void ui_repaintInlineItem(const InlineItem *layout); + bool ui_isInlineItemVisible(const InlineItem *layout); bool ui_isInlineItemBeingChosen(); bool inlineResultsShown() const { @@ -368,11 +382,7 @@ public: } int32 countHeight(bool plain = false); - ~StickerPanInner() { - clearInlineRows(true); - deleteUnusedGifLayouts(); - deleteUnusedInlineLayouts(); - } + ~StickerPanInner(); public slots: @@ -385,7 +395,7 @@ signals: void selected(DocumentData *sticker); void selected(PhotoData *photo); - void selected(InlineResult *result, UserData *bot); + void selected(InlineBots::Result *result, UserData *bot); void removing(quint64 setId); @@ -420,10 +430,10 @@ private: int32 _top; struct DisplayedSet { - DisplayedSet(uint64 id, int32 flags, const QString &title, int32 hoversSize, const StickerPack &pack = StickerPack()) : id(id), flags(flags), title(title), hovers(hoversSize, 0), pack(pack) { + DisplayedSet(uint64 id, MTPDstickerSet::Flags flags, const QString &title, int32 hoversSize, const StickerPack &pack = StickerPack()) : id(id), flags(flags), title(title), hovers(hoversSize, 0), pack(pack) { } uint64 id; - int32 flags; + MTPDstickerSet::Flags flags; QString title; QVector hovers; StickerPack pack; @@ -439,7 +449,7 @@ private: QTimer _updateInlineItems; bool _inlineWithThumb; - typedef QVector InlineItems; + typedef QVector InlineItems; struct InlineRow { InlineRow() : height(0) { } @@ -450,13 +460,13 @@ private: InlineRows _inlineRows; void clearInlineRows(bool resultsDeleted); - typedef QMap GifLayouts; + using GifLayouts = QMap; GifLayouts _gifLayouts; - LayoutInlineGif *layoutPrepareSavedGif(DocumentData *doc, int32 position); + InlineItem *layoutPrepareSavedGif(DocumentData *doc, int32 position); - typedef QMap InlineLayouts; + using InlineLayouts = QMap; InlineLayouts _inlineLayouts; - LayoutInlineItem *layoutPrepareInlineResult(InlineResult *result, int32 position); + InlineItem *layoutPrepareInlineResult(InlineResult *result, int32 position); bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth); bool inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force = false); @@ -469,7 +479,6 @@ private: int32 validateExistingInlineRows(const InlineResults &results); int32 _selected, _pressedSel; QPoint _lastMousePos; - TextLinkPtr _linkOver, _linkDown; LinkButton _settings; @@ -482,7 +491,7 @@ class EmojiPanel : public TWidget { public: - EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY); // NoneStickerSetId if in emoji + EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY); // Stickers::NoneSetId if in emoji void setText(const QString &text); void setDeleteVisible(bool isVisible); @@ -532,6 +541,8 @@ protected: }; +} // namespace internal + class EmojiPan : public TWidget, public RPCSender { Q_OBJECT @@ -580,8 +591,8 @@ public: ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } - void ui_repaintInlineItem(const LayoutInlineItem *layout); - bool ui_isInlineItemVisible(const LayoutInlineItem *layout); + void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout); + bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout); bool ui_isInlineItemBeingChosen(); bool inlineResultsShown() const { @@ -622,7 +633,7 @@ signals: void emojiSelected(EmojiPtr emoji); void stickerSelected(DocumentData *sticker); void photoSelected(PhotoData *photo); - void inlineResultSelected(InlineResult *result, UserData *bot); + void inlineResultSelected(InlineBots::Result *result, UserData *bot); void updateStickers(); @@ -642,7 +653,7 @@ private: void updateIcons(); void prepareTab(int32 &left, int32 top, int32 _width, FlatRadiobutton &tab); - void updatePanelsPositions(const QVector &panels, int32 st); + void updatePanelsPositions(const QVector &panels, int32 st); void showAll(); void hideAll(); @@ -661,7 +672,7 @@ private: BoxShadow _shadow; FlatRadiobutton _recent, _people, _nature, _food, _activity, _travel, _objects, _symbols; - QList _icons; + QList _icons; QVector _iconHovers; int32 _iconOver, _iconSel, _iconDown; bool _iconsDragging; @@ -681,13 +692,13 @@ private: Animation _a_slide; ScrollArea e_scroll; - EmojiPanInner e_inner; - QVector e_panels; - EmojiSwitchButton e_switch; + internal::EmojiPanInner e_inner; + QVector e_panels; + internal::EmojiSwitchButton e_switch; ScrollArea s_scroll; - StickerPanInner s_inner; - QVector s_panels; - EmojiSwitchButton s_switch; + internal::StickerPanInner s_inner; + QVector s_panels; + internal::EmojiSwitchButton s_switch; uint64 _removingSetId; @@ -699,13 +710,9 @@ private: clearResults(); } QString nextOffset; - InlineResults results; - void clearResults() { - for (int32 i = 0, l = results.size(); i < l; ++i) { - delete results.at(i); - } - results.clear(); - } + QString switchPmText, switchPmStartParam; + internal::InlineResults results; + void clearResults(); }; typedef QMap InlineCache; InlineCache _inlineCache; @@ -734,18 +741,20 @@ class MentionsInner : public TWidget { public: - MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows); + MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows); void paintEvent(QPaintEvent *e); + void resizeEvent(QResizeEvent *e); void enterEvent(QEvent *e); void leaveEvent(QEvent *e); void mousePressEvent(QMouseEvent *e); void mouseMoveEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); - void clearSel(); - bool moveSel(int direction); + void clearSel(bool hidden = false); + bool moveSel(int key); bool select(); void setRecentInlineBotsInRows(int32 bots); @@ -755,27 +764,35 @@ public: signals: void chosen(QString mentionOrHashtag); + void selected(DocumentData *sticker); void mustScrollTo(int scrollToTop, int scrollToBottom); public slots: void onParentGeometryChanged(); void onUpdateSelected(bool force = false); + void onPreview(); private: + void updateSelectedRow(); void setSel(int sel, bool scroll = false); MentionsDropdown *_parent; MentionRows *_mrows; HashtagRows *_hrows; BotCommandRows *_brows; - int32 _recentInlineBotsInRows; - int32 _sel; + StickerPack *_srows; + int32 _stickersPerRow, _recentInlineBotsInRows; + int32 _sel, _down; bool _mouseSel; QPoint _mousePos; bool _overDelete; + + bool _previewShown; + + QTimer _previewTimer; }; class MentionsDropdown : public TWidget { @@ -791,7 +808,8 @@ public: bool clearFilteredBotCommands(); void showFiltered(PeerData *peer, QString query, bool start); - void updateFiltered(bool toDown = false); + void showStickers(EmojiPtr emoji); + void updateFiltered(bool resetScroll = false); void setBoundings(QRect boundings); void step_appearance(float64 ms, bool timer); @@ -807,6 +825,10 @@ public: bool eventFilter(QObject *obj, QEvent *e); QString getSelected() const; + bool stickersShown() const { + return !_srows.isEmpty(); + } + bool overlaps(const QRect &globalRect) { if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false; @@ -818,6 +840,7 @@ public: signals: void chosen(QString mentionOrHashtag); + void stickerSelected(DocumentData *sticker); public slots: @@ -828,14 +851,15 @@ public slots: private: - void recount(bool toDown = false); + void recount(bool resetScroll = false); QPixmap _cache; MentionRows _mrows; HashtagRows _hrows; BotCommandRows _brows; + StickerPack _srows; - void rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, bool toDown); + void rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, const StickerPack &srows, bool resetScroll); ScrollArea _scroll; MentionsInner _inner; @@ -843,6 +867,7 @@ private: ChatData *_chat; UserData *_user; ChannelData *_channel; + EmojiPtr _emoji; QString _filter; QRect _boundings; bool _addInlineBots; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 09b1327402..fb2bf04e51 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -16,19 +16,26 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "window.h" #include "mainwidget.h" +#include "application.h" + +#include "boxes/confirmbox.h" #include "layerwidget.h" +#include "lang.h" + +Q_DECLARE_METATYPE(ClickHandlerPtr); +Q_DECLARE_METATYPE(Qt::MouseButton); namespace App { - void sendBotCommand(const QString &cmd, MsgId replyTo) { - if (MainWidget *m = main()) m->sendBotCommand(cmd, replyTo); + void sendBotCommand(PeerData *peer, const QString &cmd, MsgId replyTo) { + if (MainWidget *m = main()) m->sendBotCommand(peer, cmd, replyTo); } bool insertBotCommand(const QString &cmd, bool specialGif) { @@ -36,12 +43,55 @@ namespace App { return false; } - void searchByHashtag(const QString &tag, PeerData *inPeer) { - if (MainWidget *m = main()) m->searchMessages(tag + ' ', (inPeer && inPeer->isChannel()) ? inPeer : 0); + void activateBotCommand(const HistoryItem *msg, int row, int col) { + const HistoryMessageReplyMarkup::Button *button = nullptr; + if (auto *markup = msg->Get()) { + if (row < markup->rows.size()) { + const HistoryMessageReplyMarkup::ButtonRow &buttonRow(markup->rows.at(row)); + if (col < buttonRow.size()) { + button = &buttonRow.at(col); + } + } + } + if (!button) return; + + switch (button->type) { + case HistoryMessageReplyMarkup::Button::Default: { + // Copy string before passing it to the sending method + // because the original button can be destroyed inside. + MsgId replyTo = (msg->id > 0) ? msg->id : 0; + sendBotCommand(msg->history()->peer, QString(button->text), replyTo); + } break; + + case HistoryMessageReplyMarkup::Button::Callback: { + if (MainWidget *m = main()) { + m->app_sendBotCallback(button, msg, row, col); + } + } break; + + case HistoryMessageReplyMarkup::Button::Url: { + auto url = QString::fromUtf8(button->data); + HiddenUrlClickHandler(url).onClick(Qt::LeftButton); + } break; + + case HistoryMessageReplyMarkup::Button::RequestLocation: { + Ui::showLayer(new InformBox(lang(lng_bot_share_location_unavailable))); + } break; + + case HistoryMessageReplyMarkup::Button::RequestPhone: { + SharePhoneConfirmBox *box = new SharePhoneConfirmBox(msg->history()->peer); + box->connect(box, SIGNAL(confirmed(PeerData*)), App::main(), SLOT(onSharePhoneWithBot(PeerData*))); + Ui::showLayer(box); + } break; + } } - void openPeerByName(const QString &username, bool toProfile, const QString &startToken) { - if (MainWidget *m = main()) m->openPeerByName(username, toProfile, startToken); + void searchByHashtag(const QString &tag, PeerData *inPeer) { + if (MainWidget *m = main()) m->searchMessages(tag + ' ', (inPeer && inPeer->isChannel() && !inPeer->isMegagroup()) ? inPeer : 0); + } + + void openPeerByName(const QString &username, MsgId msgId, const QString &startToken) { + if (MainWidget *m = main()) m->openPeerByName(username, msgId, startToken); } void joinGroupByHash(const QString &hash) { @@ -62,11 +112,29 @@ namespace App { } void removeDialog(History *history) { - if (MainWidget *m = main()) m->removeDialog(history); + if (MainWidget *m = main()) { + m->removeDialog(history); + } } void showSettings() { - if (Window *win = wnd()) win->showSettings(); + if (Window *w = wnd()) { + w->showSettings(); + } + } + + void activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button) { + if (Window *w = wnd()) { + qRegisterMetaType(); + qRegisterMetaType(); + QMetaObject::invokeMethod(w, "app_activateClickHandler", Qt::QueuedConnection, Q_ARG(ClickHandlerPtr, handler), Q_ARG(Qt::MouseButton, button)); + } + } + + void logOutDelayed() { + if (Window *w = App::wnd()) { + QMetaObject::invokeMethod(w, "onLogoutSure", Qt::QueuedConnection); + } } } @@ -74,11 +142,15 @@ namespace App { namespace Ui { void showStickerPreview(DocumentData *sticker) { - if (MainWidget *m = App::main()) m->ui_showStickerPreview(sticker); + if (Window *w = App::wnd()) { + w->ui_showStickerPreview(sticker); + } } void hideStickerPreview() { - if (MainWidget *m = App::main()) m->ui_hideStickerPreview(); + if (Window *w = App::wnd()) { + w->ui_hideStickerPreview(); + } } void showLayer(LayeredWidget *box, ShowLayerOptions options) { @@ -113,16 +185,22 @@ namespace Ui { if (MainWidget *m = App::main()) m->ui_repaintHistoryItem(item); } - void repaintInlineItem(const LayoutInlineItem *layout) { + void repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { if (!layout) return; if (MainWidget *m = App::main()) m->ui_repaintInlineItem(layout); } - bool isInlineItemVisible(const LayoutInlineItem *layout) { + bool isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { if (MainWidget *m = App::main()) return m->ui_isInlineItemVisible(layout); return false; } + void autoplayMediaInlineAsync(const FullMsgId &msgId) { + if (MainWidget *m = App::main()) { + QMetaObject::invokeMethod(m, "ui_autoplayMediaInlineAsync", Qt::QueuedConnection, Q_ARG(qint32, msgId.channel), Q_ARG(qint32, msgId.msg)); + } + } + void showPeerHistory(const PeerId &peer, MsgId msgId, bool back) { if (MainWidget *m = App::main()) m->ui_showPeerHistory(peer, msgId, back); } @@ -133,6 +211,29 @@ namespace Ui { } } + PeerData *getPeerForMouseAction() { + if (Window *w = App::wnd()) { + return w->ui_getPeerForMouseAction(); + } + return nullptr; + } + + bool hideWindowNoQuit() { + if (!App::quitting()) { + if (Window *w = App::wnd()) { + if (cWorkMode() == dbiwmTrayOnly || cWorkMode() == dbiwmWindowAndTray) { + return w->minimizeToTray(); + } else if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { + w->hide(); + w->updateIsActive(Global::OfflineBlurTimeout()); + w->updateGlobalMenu(); + return true; + } + } + } + return false; + } + } namespace Notify { @@ -153,6 +254,12 @@ namespace Notify { if (MainWidget *m = App::main()) m->notify_inlineBotRequesting(requesting); } + void replyMarkupUpdated(const HistoryItem *item) { + if (MainWidget *m = App::main()) { + m->notify_replyMarkupUpdated(item); + } + } + void migrateUpdated(PeerData *peer) { if (MainWidget *m = App::main()) m->notify_migrateUpdated(peer); } @@ -161,10 +268,6 @@ namespace Notify { if (MainWidget *m = App::main()) m->notify_clipStopperHidden(type); } - void historyItemResized(const HistoryItem *item, bool scrollToIt) { - if (MainWidget *m = App::main()) m->notify_historyItemResized(item, scrollToIt); - } - void historyItemLayoutChanged(const HistoryItem *item) { if (MainWidget *m = App::main()) m->notify_historyItemLayoutChanged(item); } @@ -173,4 +276,263 @@ namespace Notify { if (MainWidget *m = App::main()) m->notify_automaticLoadSettingsChangedGif(); } + void handlePendingHistoryUpdate() { + if (MainWidget *m = App::main()) { + m->notify_handlePendingHistoryUpdate(); + } + for_const (HistoryItem *item, Global::PendingRepaintItems()) { + Ui::repaintHistoryItem(item); + } + Global::RefPendingRepaintItems().clear(); + } + } + +#define DefineReadOnlyVar(Namespace, Type, Name) const Type &Name() { \ + t_assert_full(Namespace##Data != 0, #Namespace "Data != nullptr in " #Namespace "::" #Name, __FILE__, __LINE__); \ + return Namespace##Data->Name; \ +} +#define DefineRefVar(Namespace, Type, Name) DefineReadOnlyVar(Namespace, Type, Name) \ +Type &Ref##Name() { \ + t_assert_full(Namespace##Data != 0, #Namespace "Data != nullptr in " #Namespace "::Ref" #Name, __FILE__, __LINE__); \ + return Namespace##Data->Name; \ +} +#define DefineVar(Namespace, Type, Name) DefineRefVar(Namespace, Type, Name) \ +void Set##Name(const Type &Name) { \ + t_assert_full(Namespace##Data != 0, #Namespace "Data != nullptr in " #Namespace "::Set" #Name, __FILE__, __LINE__); \ + Namespace##Data->Name = Name; \ +} + +namespace Sandbox { + + namespace internal { + + struct Data { + QString LangSystemISO; + int32 LangSystem = languageDefault; + + QByteArray LastCrashDump; + ConnectionProxy PreLaunchProxy; + }; + + } + +} +Sandbox::internal::Data *SandboxData = 0; +uint64 SandboxUserTag = 0; + +namespace Sandbox { + + bool CheckBetaVersionDir() { + QFile beta(cExeDir() + qsl("TelegramBeta_data/tdata/beta")); + if (cBetaVersion()) { + cForceWorkingDir(cExeDir() + qsl("TelegramBeta_data/")); + QDir().mkpath(cWorkingDir() + qstr("tdata")); + if (*BetaPrivateKey) { + cSetBetaPrivateKey(QByteArray(BetaPrivateKey)); + } + if (beta.open(QIODevice::WriteOnly)) { + QDataStream dataStream(&beta); + dataStream.setVersion(QDataStream::Qt_5_3); + dataStream << quint64(cRealBetaVersion()) << cBetaPrivateKey(); + } else { + LOG(("FATAL: Could not open '%1' for writing private key!").arg(beta.fileName())); + return false; + } + } else if (beta.exists()) { + cForceWorkingDir(cExeDir() + qsl("TelegramBeta_data/")); + if (beta.open(QIODevice::ReadOnly)) { + QDataStream dataStream(&beta); + dataStream.setVersion(QDataStream::Qt_5_3); + + quint64 v; + QByteArray k; + dataStream >> v >> k; + if (dataStream.status() == QDataStream::Ok) { + cSetBetaVersion(qMax(v, AppVersion * 1000ULL)); + cSetBetaPrivateKey(k); + cSetRealBetaVersion(v); + } else { + LOG(("FATAL: '%1' is corrupted, reinstall private beta!").arg(beta.fileName())); + return false; + } + } else { + LOG(("FATAL: could not open '%1' for reading private key!").arg(beta.fileName())); + return false; + } + } + return true; + } + + void WorkingDirReady() { + if (QFile(cWorkingDir() + qsl("tdata/withtestmode")).exists()) { + cSetTestMode(true); + } + if (!cDebug() && QFile(cWorkingDir() + qsl("tdata/withdebug")).exists()) { + cSetDebug(true); + } + if (cBetaVersion()) { + cSetDevVersion(false); + } else if (!cDevVersion() && QFile(cWorkingDir() + qsl("tdata/devversion")).exists()) { + cSetDevVersion(true); + } else if (DevVersion) { + QFile f(cWorkingDir() + qsl("tdata/devversion")); + if (!f.exists() && f.open(QIODevice::WriteOnly)) { + f.write("1"); + } + } + + srand((int32)time(NULL)); + + SandboxUserTag = 0; + QFile usertag(cWorkingDir() + qsl("tdata/usertag")); + if (usertag.open(QIODevice::ReadOnly)) { + if (usertag.read(reinterpret_cast(&SandboxUserTag), sizeof(uint64)) != sizeof(uint64)) { + SandboxUserTag = 0; + } + usertag.close(); + } + if (!SandboxUserTag) { + do { + memsetrnd_bad(SandboxUserTag); + } while (!SandboxUserTag); + + if (usertag.open(QIODevice::WriteOnly)) { + usertag.write(reinterpret_cast(&SandboxUserTag), sizeof(uint64)); + usertag.close(); + } + } + } + + void start() { + SandboxData = new internal::Data(); + + SandboxData->LangSystemISO = psCurrentLanguage(); + if (SandboxData->LangSystemISO.isEmpty()) SandboxData->LangSystemISO = qstr("en"); + QByteArray l = LangSystemISO().toLatin1(); + for (int32 i = 0; i < languageCount; ++i) { + if (l.at(0) == LanguageCodes[i][0] && l.at(1) == LanguageCodes[i][1]) { + SandboxData->LangSystem = i; + break; + } + } + } + + void finish() { + delete SandboxData; + SandboxData = 0; + } + + uint64 UserTag() { + return SandboxUserTag; + } + + DefineReadOnlyVar(Sandbox, QString, LangSystemISO); + DefineReadOnlyVar(Sandbox, int32, LangSystem); + DefineVar(Sandbox, QByteArray, LastCrashDump); + DefineVar(Sandbox, ConnectionProxy, PreLaunchProxy); + +} + +namespace Global { + namespace internal { + + struct Data { + uint64 LaunchId = 0; + SingleDelayedCall HandleHistoryUpdate = { App::app(), "call_handleHistoryUpdate" }; + + Adaptive::Layout AdaptiveLayout = Adaptive::NormalLayout; + bool AdaptiveForWide = true; + + int32 DebugLoggingFlags = 0; + + // config + int32 ChatSizeMax = 200; + int32 MegagroupSizeMax = 1000; + int32 ForwardedCountMax = 100; + int32 OnlineUpdatePeriod = 120000; + int32 OfflineBlurTimeout = 5000; + int32 OfflineIdleTimeout = 30000; + int32 OnlineFocusTimeout = 1000; + int32 OnlineCloudTimeout = 300000; + int32 NotifyCloudDelay = 30000; + int32 NotifyDefaultDelay = 1500; + int32 ChatBigSize = 10; + int32 PushChatPeriod = 60000; + int32 PushChatLimit = 2; + int32 SavedGifsLimit = 200; + int32 EditTimeLimit = 172800; + + HiddenPinnedMessagesMap HiddenPinnedMessages; + + PendingItemsMap PendingRepaintItems; + + Stickers::Sets StickerSets; + Stickers::Order StickerSetsOrder; + uint64 LastStickersUpdate = 0; + + MTP::DcOptions DcOptions; + + CircleMasksMap CircleMasks; + }; + + } +} + +Global::internal::Data *GlobalData = 0; + +namespace Global { + + bool started() { + return GlobalData != 0; + } + + void start() { + GlobalData = new internal::Data(); + + memset_rand(&GlobalData->LaunchId, sizeof(GlobalData->LaunchId)); + } + + void finish() { + delete GlobalData; + GlobalData = 0; + } + + DefineReadOnlyVar(Global, uint64, LaunchId); + DefineRefVar(Global, SingleDelayedCall, HandleHistoryUpdate); + + DefineVar(Global, Adaptive::Layout, AdaptiveLayout); + DefineVar(Global, bool, AdaptiveForWide); + + DefineVar(Global, int32, DebugLoggingFlags); + + // config + DefineVar(Global, int32, ChatSizeMax); + DefineVar(Global, int32, MegagroupSizeMax); + DefineVar(Global, int32, ForwardedCountMax); + DefineVar(Global, int32, OnlineUpdatePeriod); + DefineVar(Global, int32, OfflineBlurTimeout); + DefineVar(Global, int32, OfflineIdleTimeout); + DefineVar(Global, int32, OnlineFocusTimeout); + DefineVar(Global, int32, OnlineCloudTimeout); + DefineVar(Global, int32, NotifyCloudDelay); + DefineVar(Global, int32, NotifyDefaultDelay); + DefineVar(Global, int32, ChatBigSize); + DefineVar(Global, int32, PushChatPeriod); + DefineVar(Global, int32, PushChatLimit); + DefineVar(Global, int32, SavedGifsLimit); + DefineVar(Global, int32, EditTimeLimit); + + DefineVar(Global, HiddenPinnedMessagesMap, HiddenPinnedMessages); + + DefineRefVar(Global, PendingItemsMap, PendingRepaintItems); + + DefineVar(Global, Stickers::Sets, StickerSets); + DefineVar(Global, Stickers::Order, StickerSetsOrder); + DefineVar(Global, uint64, LastStickersUpdate); + + DefineVar(Global, MTP::DcOptions, DcOptions); + + DefineRefVar(Global, CircleMasksMap, CircleMasks); + +}; diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index ed7ee718cf..8adec8d7f9 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -24,10 +24,11 @@ class LayeredWidget; namespace App { - void sendBotCommand(const QString &cmd, MsgId replyTo = 0); + void sendBotCommand(PeerData *peer, const QString &cmd, MsgId replyTo = 0); bool insertBotCommand(const QString &cmd, bool specialGif = false); + void activateBotCommand(const HistoryItem *msg, int row, int col); void searchByHashtag(const QString &tag, PeerData *inPeer); - void openPeerByName(const QString &username, bool toProfile = false, const QString &startToken = QString()); + void openPeerByName(const QString &username, MsgId msgId = ShowAtUnreadMsgId, const QString &startToken = QString()); void joinGroupByHash(const QString &hash); void stickersBox(const QString &name); void openLocalUrl(const QString &url); @@ -35,9 +36,21 @@ namespace App { void removeDialog(History *history); void showSettings(); + void activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button); + + void logOutDelayed(); + }; -namespace Ui { // openssl doesn't allow me to use UI :( +namespace InlineBots { +namespace Layout { + +class ItemBase; + +} // namespace Layout +} // namespace InlineBots + +namespace Ui { void showStickerPreview(DocumentData *sticker); void hideStickerPreview(); @@ -49,8 +62,9 @@ namespace Ui { // openssl doesn't allow me to use UI :( bool isInlineItemBeingChosen(); void repaintHistoryItem(const HistoryItem *item); - void repaintInlineItem(const LayoutInlineItem *layout); - bool isInlineItemVisible(const LayoutInlineItem *reader); + void repaintInlineItem(const InlineBots::Layout::ItemBase *layout); + bool isInlineItemVisible(const InlineBots::Layout::ItemBase *reader); + void autoplayMediaInlineAsync(const FullMsgId &msgId); void showPeerHistory(const PeerId &peer, MsgId msgId, bool back = false); inline void showPeerHistory(const PeerData *peer, MsgId msgId, bool back = false) { @@ -66,6 +80,12 @@ namespace Ui { // openssl doesn't allow me to use UI :( inline void showChatsList() { showPeerHistory(PeerId(0), 0); } + inline void showChatsListAsync() { + showPeerHistoryAsync(PeerId(0), 0); + } + PeerData *getPeerForMouseAction(); + + bool hideWindowNoQuit(); }; @@ -81,17 +101,138 @@ namespace Notify { void botCommandsChanged(UserData *user); void inlineBotRequesting(bool requesting); + void replyMarkupUpdated(const HistoryItem *item); void migrateUpdated(PeerData *peer); void clipStopperHidden(ClipStopperType type); - void historyItemResized(const HistoryItem *item, bool scrollToIt = false); - inline void historyItemsResized() { - historyItemResized(0); - } void historyItemLayoutChanged(const HistoryItem *item); void automaticLoadSettingsChangedGif(); + // handle pending resize() / paint() on history items + void handlePendingHistoryUpdate(); + }; + +#define DeclareReadOnlyVar(Type, Name) const Type &Name(); +#define DeclareRefVar(Type, Name) DeclareReadOnlyVar(Type, Name) \ + Type &Ref##Name(); +#define DeclareVar(Type, Name) DeclareRefVar(Type, Name) \ + void Set##Name(const Type &Name); + +namespace Sandbox { + + bool CheckBetaVersionDir(); + void WorkingDirReady(); + + void start(); + void finish(); + + uint64 UserTag(); + + DeclareReadOnlyVar(QString, LangSystemISO); + DeclareReadOnlyVar(int32, LangSystem); + DeclareVar(QByteArray, LastCrashDump); + DeclareVar(ConnectionProxy, PreLaunchProxy); + +} + +namespace Adaptive { + enum Layout { + OneColumnLayout, + NormalLayout, + WideLayout, + }; +}; + +namespace DebugLogging { + enum Flags { + FileLoaderFlag = 0x00000001, + }; +} + +namespace Stickers { + static const uint64 DefaultSetId = 0; // for backward compatibility + static const uint64 CustomSetId = 0xFFFFFFFFFFFFFFFFULL, RecentSetId = 0xFFFFFFFFFFFFFFFEULL; + static const uint64 NoneSetId = 0xFFFFFFFFFFFFFFFDULL; // for emoji/stickers panel + struct Set { + Set(uint64 id, uint64 access, const QString &title, const QString &shortName, int32 count, int32 hash, MTPDstickerSet::Flags flags) : id(id), access(access), title(title), shortName(shortName), count(count), hash(hash), flags(flags) { + } + uint64 id, access; + QString title, shortName; + int32 count, hash; + MTPDstickerSet::Flags flags; + StickerPack stickers; + StickersByEmojiMap emoji; + }; + typedef QMap Sets; + typedef QList Order; +} + +namespace Global { + + bool started(); + void start(); + void finish(); + + DeclareReadOnlyVar(uint64, LaunchId); + DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate); + + DeclareVar(Adaptive::Layout, AdaptiveLayout); + DeclareVar(bool, AdaptiveForWide); + + DeclareVar(int32, DebugLoggingFlags); + + // config + DeclareVar(int32, ChatSizeMax); + DeclareVar(int32, MegagroupSizeMax); + DeclareVar(int32, ForwardedCountMax); + DeclareVar(int32, OnlineUpdatePeriod); + DeclareVar(int32, OfflineBlurTimeout); + DeclareVar(int32, OfflineIdleTimeout); + DeclareVar(int32, OnlineFocusTimeout); // not from config + DeclareVar(int32, OnlineCloudTimeout); + DeclareVar(int32, NotifyCloudDelay); + DeclareVar(int32, NotifyDefaultDelay); + DeclareVar(int32, ChatBigSize); + DeclareVar(int32, PushChatPeriod); + DeclareVar(int32, PushChatLimit); + DeclareVar(int32, SavedGifsLimit); + DeclareVar(int32, EditTimeLimit); + + typedef QMap HiddenPinnedMessagesMap; + DeclareVar(HiddenPinnedMessagesMap, HiddenPinnedMessages); + + typedef OrderedSet PendingItemsMap; + DeclareRefVar(PendingItemsMap, PendingRepaintItems); + + DeclareVar(Stickers::Sets, StickerSets); + DeclareVar(Stickers::Order, StickerSetsOrder); + DeclareVar(uint64, LastStickersUpdate); + + DeclareVar(MTP::DcOptions, DcOptions); + + typedef QMap CircleMasksMap; + DeclareRefVar(CircleMasksMap, CircleMasks); + +}; + +namespace Adaptive { + inline bool OneColumn() { + return Global::AdaptiveLayout() == OneColumnLayout; + } + inline bool Normal() { + return Global::AdaptiveLayout() == NormalLayout; + } + inline bool Wide() { + return Global::AdaptiveForWide() && (Global::AdaptiveLayout() == WideLayout); + } +} + +namespace DebugLogging { + inline bool FileLoader() { + return (Global::DebugLoggingFlags() | FileLoaderFlag) != 0; + } +} diff --git a/Telegram/SourceFiles/fileuploader.cpp b/Telegram/SourceFiles/fileuploader.cpp index 2c5f30adc7..c7d471818f 100644 --- a/Telegram/SourceFiles/fileuploader.cpp +++ b/Telegram/SourceFiles/fileuploader.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "fileuploader.h" @@ -32,7 +32,7 @@ FileUploader::FileUploader() : sentSize(0) { void FileUploader::uploadMedia(const FullMsgId &msgId, const ReadyLocalMedia &media) { if (media.type == PreparePhoto) { App::feedPhoto(media.photo, media.photoThumbs); - } else if (media.type == PrepareDocument) { + } else if (media.type == PrepareDocument || media.type == PrepareAudio) { DocumentData *document; if (media.photoThumbs.isEmpty()) { document = App::feedDocument(media.document); @@ -40,13 +40,12 @@ void FileUploader::uploadMedia(const FullMsgId &msgId, const ReadyLocalMedia &me document = App::feedDocument(media.document, media.photoThumbs.begin().value()); } document->status = FileUploading; + if (!media.data.isEmpty()) { + document->setData(media.data); + } if (!media.file.isEmpty()) { document->setLocation(FileLocation(StorageFilePartial, media.file)); } - } else if (media.type == PrepareAudio) { - AudioData *audio = App::feedAudio(media.audio); - audio->status = FileUploading; - audio->setData(media.data); } queue.insert(msgId, File(media)); sendNext(); @@ -56,7 +55,7 @@ void FileUploader::upload(const FullMsgId &msgId, const FileLoadResultPtr &file) if (file->type == PreparePhoto) { PhotoData *photo = App::feedPhoto(file->photo, file->photoThumbs); photo->uploadingData = new PhotoData::UploadingData(file->partssize); - } else if (file->type == PrepareDocument) { + } else if (file->type == PrepareDocument || file->type == PrepareAudio) { DocumentData *document; if (file->thumb.isNull()) { document = App::feedDocument(file->document); @@ -64,13 +63,12 @@ void FileUploader::upload(const FullMsgId &msgId, const FileLoadResultPtr &file) document = App::feedDocument(file->document, file->thumb); } document->status = FileUploading; + if (!file->content.isEmpty()) { + document->setData(file->content); + } if (!file->filepath.isEmpty()) { document->setLocation(FileLocation(StorageFilePartial, file->filepath)); } - } else if (file->type == PrepareAudio) { - AudioData *audio = App::feedAudio(file->audio); - audio->status = FileUploading; - audio->setData(file->content); } queue.insert(msgId, File(file)); sendNext(); @@ -87,12 +85,6 @@ void FileUploader::currentFailed() { doc->status = FileUploadFailed; } emit documentFailed(j.key()); - } else if (j->type() == PrepareAudio) { - AudioData *audio = App::audio(j->id()); - if (audio->status == FileUploading) { - audio->status = FileUploadFailed; - } - emit audioFailed(j.key()); } queue.erase(j); } @@ -111,7 +103,7 @@ void FileUploader::currentFailed() { void FileUploader::killSessions() { for (int i = 0; i < MTPUploadSessionsCount; ++i) { - MTP::stopSession(MTP::upl[i]); + MTP::stopSession(MTP::uplDcId(i)); } } @@ -133,7 +125,7 @@ void FileUploader::sendNext() { if (!uploading.msg) { uploading = i.key(); } else if (i == queue.end()) { - i = queue.begin(); + i = queue.begin(); uploading = i.key(); } int todc = 0; @@ -148,24 +140,19 @@ void FileUploader::sendNext() { if (parts.isEmpty()) { if (i->docSentParts >= i->docPartsCount) { if (requestsSent.isEmpty() && docRequestsSent.isEmpty()) { + bool silent = i->file && i->file->to.silent; if (i->type() == PreparePhoto) { - emit photoReady(uploading, MTP_inputFile(MTP_long(i->id()), MTP_int(i->partsCount), MTP_string(i->filename()), MTP_string(i->file ? i->file->filemd5 : i->media.jpeg_md5))); - } else if (i->type() == PrepareDocument) { + emit photoReady(uploading, silent, MTP_inputFile(MTP_long(i->id()), MTP_int(i->partsCount), MTP_string(i->filename()), MTP_bytes(i->file ? i->file->filemd5 : i->media.jpeg_md5))); + } else if (i->type() == PrepareDocument || i->type() == PrepareAudio) { QByteArray docMd5(32, Qt::Uninitialized); hashMd5Hex(i->md5Hash.result(), docMd5.data()); - MTPInputFile doc = (i->docSize > UseBigFilesFrom) ? MTP_inputFileBig(MTP_long(i->id()), MTP_int(i->docPartsCount), MTP_string(i->filename())) : MTP_inputFile(MTP_long(i->id()), MTP_int(i->docPartsCount), MTP_string(i->filename()), MTP_string(docMd5)); + MTPInputFile doc = (i->docSize > UseBigFilesFrom) ? MTP_inputFileBig(MTP_long(i->id()), MTP_int(i->docPartsCount), MTP_string(i->filename())) : MTP_inputFile(MTP_long(i->id()), MTP_int(i->docPartsCount), MTP_string(i->filename()), MTP_bytes(docMd5)); if (i->partsCount) { - emit thumbDocumentReady(uploading, doc, MTP_inputFile(MTP_long(i->thumbId()), MTP_int(i->partsCount), MTP_string(i->file ? i->file->thumbname : (qsl("thumb.") + i->media.thumbExt)), MTP_string(i->file ? i->file->thumbmd5 : i->media.jpeg_md5))); + emit thumbDocumentReady(uploading, silent, doc, MTP_inputFile(MTP_long(i->thumbId()), MTP_int(i->partsCount), MTP_string(i->file ? i->file->thumbname : (qsl("thumb.") + i->media.thumbExt)), MTP_bytes(i->file ? i->file->thumbmd5 : i->media.jpeg_md5))); } else { - emit documentReady(uploading, doc); + emit documentReady(uploading, silent, doc); } - } else if (i->type() == PrepareAudio) { - QByteArray audioMd5(32, Qt::Uninitialized); - hashMd5Hex(i->md5Hash.result(), audioMd5.data()); - - MTPInputFile audio = (i->docSize > UseBigFilesFrom) ? MTP_inputFileBig(MTP_long(i->id()), MTP_int(i->docPartsCount), MTP_string(i->filename())) : MTP_inputFile(MTP_long(i->id()), MTP_int(i->docPartsCount), MTP_string(i->filename()), MTP_string(audioMd5)); - emit audioReady(uploading, audio); } queue.remove(uploading); uploading = FullMsgId(); @@ -200,9 +187,9 @@ void FileUploader::sendNext() { } mtpRequestId requestId; if (i->docSize > UseBigFilesFrom) { - requestId = MTP::send(MTPupload_SaveBigFilePart(MTP_long(i->id()), MTP_int(i->docSentParts), MTP_int(i->docPartsCount), MTP_string(toSend)), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::upl[todc]); + requestId = MTP::send(MTPupload_SaveBigFilePart(MTP_long(i->id()), MTP_int(i->docSentParts), MTP_int(i->docPartsCount), MTP_bytes(toSend)), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::uplDcId(todc)); } else { - requestId = MTP::send(MTPupload_SaveFilePart(MTP_long(i->id()), MTP_int(i->docSentParts), MTP_string(toSend)), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::upl[todc]); + requestId = MTP::send(MTPupload_SaveFilePart(MTP_long(i->id()), MTP_int(i->docSentParts), MTP_bytes(toSend)), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::uplDcId(todc)); } docRequestsSent.insert(requestId, i->docSentParts); dcMap.insert(requestId, todc); @@ -212,8 +199,8 @@ void FileUploader::sendNext() { i->docSentParts++; } else { UploadFileParts::iterator part = parts.begin(); - - mtpRequestId requestId = MTP::send(MTPupload_SaveFilePart(MTP_long(partsOfId), MTP_int(part.key()), MTP_string(part.value())), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::upl[todc]); + + mtpRequestId requestId = MTP::send(MTPupload_SaveFilePart(MTP_long(partsOfId), MTP_int(part.key()), MTP_bytes(part.value())), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::uplDcId(todc)); requestsSent.insert(requestId, part.value()); dcMap.insert(requestId, todc); sentSize += part.value().size(); @@ -259,7 +246,7 @@ void FileUploader::clear() { dcMap.clear(); sentSize = 0; for (int32 i = 0; i < MTPUploadSessionsCount; ++i) { - MTP::stopSession(MTP::upl[i]); + MTP::stopSession(MTP::uplDcId(i)); sentSizes[i] = 0; } killSessionsTimer.stop(); @@ -303,7 +290,7 @@ void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) { photo->uploadingData->offset = k->fileSentSize; } emit photoProgress(k.key()); - } else if (k->type() == PrepareDocument) { + } else if (k->type() == PrepareDocument || k->type() == PrepareAudio) { DocumentData *doc = App::document(k->id()); if (doc->uploading()) { doc->uploadOffset = (k->docSentParts - docRequestsSent.size()) * k->docPartSize; @@ -312,15 +299,6 @@ void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) { } } emit documentProgress(k.key()); - } else if (k->type() == PrepareAudio) { - AudioData *audio = App::audio(k->id()); - if (audio->uploading()) { - audio->uploadOffset = (k->docSentParts - docRequestsSent.size()) * k->docPartSize; - if (audio->uploadOffset > audio->size) { - audio->uploadOffset = audio->size; - } - } - emit audioProgress(k.key()); } } } diff --git a/Telegram/SourceFiles/fileuploader.h b/Telegram/SourceFiles/fileuploader.h index 460f8e1702..5ae1c6dbcd 100644 --- a/Telegram/SourceFiles/fileuploader.h +++ b/Telegram/SourceFiles/fileuploader.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -48,18 +48,15 @@ public slots: signals: - void photoReady(const FullMsgId &msgId, const MTPInputFile &file); - void documentReady(const FullMsgId &msgId, const MTPInputFile &file); - void thumbDocumentReady(const FullMsgId &msgId, const MTPInputFile &file, const MTPInputFile &thumb); - void audioReady(const FullMsgId &msgId, const MTPInputFile &file); + void photoReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file); + void documentReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file); + void thumbDocumentReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb); void photoProgress(const FullMsgId &msgId); void documentProgress(const FullMsgId &msgId); - void audioProgress(const FullMsgId &msgId); void photoFailed(const FullMsgId &msgId); void documentFailed(const FullMsgId &msgId); - void audioFailed(const FullMsgId &msgId); private: @@ -138,7 +135,7 @@ private: QMap dcMap; uint32 sentSize; uint32 sentSizes[MTPUploadSessionsCount]; - + FullMsgId uploading, _paused; Queue queue; Queue uploaded; diff --git a/Telegram/SourceFiles/gui/animation.cpp b/Telegram/SourceFiles/gui/animation.cpp index ee49bb92c1..047a3346b3 100644 --- a/Telegram/SourceFiles/gui/animation.cpp +++ b/Telegram/SourceFiles/gui/animation.cpp @@ -16,12 +16,19 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "animation.h" +extern "C" { +#include +#include +#include +#include +} + #include "mainwidget.h" #include "window.h" @@ -93,6 +100,7 @@ namespace anim { if (!_clipThreads.isEmpty()) { for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) { _clipThreads.at(i)->quit(); + DEBUG_LOG(("Waiting for clipThread to finish: %1").arg(i)); _clipThreads.at(i)->wait(); delete _clipManagers.at(i); delete _clipThreads.at(i); @@ -142,7 +150,7 @@ void AnimationManager::stop(Animation *obj) { if (_iterating) { _stopping.insert(obj, NullType()); if (!_starting.isEmpty()) { - _starting.insert(obj, NullType()); + _starting.remove(obj); } } else { AnimatingObjects::iterator i = _objects.find(obj); @@ -159,7 +167,9 @@ void AnimationManager::timeout() { _iterating = true; uint64 ms = getms(); for (AnimatingObjects::const_iterator i = _objects.begin(), e = _objects.end(); i != e; ++i) { - i.key()->step(ms, true); + if (!_stopping.contains(i.key())) { + i.key()->step(ms, true); + } } _iterating = false; @@ -189,18 +199,25 @@ QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, b bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh); if (badSize || needOuter || hasAlpha || request.rounded) { int32 factor(request.factor); - bool fill = false; - if (cache.width() != request.outerw || cache.height() != request.outerh) { + bool newcache = (cache.width() != request.outerw || cache.height() != request.outerh); + if (newcache) { cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied); - if (request.framew < request.outerw || request.frameh < request.outerh || hasAlpha) { - fill = true; - } cache.setDevicePixelRatio(factor); } { Painter p(&cache); - if (fill) { - p.fillRect(0, 0, cache.width() / factor, cache.height() / factor, st::black); + if (newcache) { + if (request.framew < request.outerw) { + p.fillRect(0, 0, (request.outerw - request.framew) / (2 * factor), cache.height() / factor, st::black); + p.fillRect((request.outerw - request.framew) / (2 * factor) + (request.framew / factor), 0, (cache.width() / factor) - ((request.outerw - request.framew) / (2 * factor) + (request.framew / factor)), cache.height() / factor, st::black); + } + if (request.frameh < request.outerh) { + p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), 0, qMin(cache.width(), request.framew) / factor, (request.outerh - request.frameh) / (2 * factor), st::black); + p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), (request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor), qMin(cache.width(), request.framew) / factor, (cache.height() / factor) - ((request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor)), st::black); + } + } + if (hasAlpha) { + p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), qMax(0, (request.outerh - request.frameh) / (2 * factor)), qMin(cache.width(), request.framew) / factor, qMin(cache.height(), request.frameh) / factor, st::white); } QPoint position((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor)); if (badSize) { @@ -235,7 +252,7 @@ ClipReader::ClipReader(const FileLocation &location, const QByteArray &data, Cal _clipManagers.push_back(new ClipReadManager(_clipThreads.back())); _clipThreads.back()->start(); } else { - _threadIndex = int32(MTP::nonce() % _clipThreads.size()); + _threadIndex = int32(rand_value() % _clipThreads.size()); int32 loadLevel = 0x7FFFFFFF; for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) { int32 level = _clipManagers.at(i)->loadLevel(); @@ -369,6 +386,7 @@ QPixmap ClipReader::current(int32 framew, int32 frameh, int32 outerw, int32 oute frame->request.outerh = outerh * factor; QImage cacheForResize; + frame->original.setDevicePixelRatio(factor); frame->pix = QPixmap(); frame->pix = _prepareFrame(frame->request, frame->original, true, cacheForResize); @@ -418,7 +436,6 @@ void ClipReader::stop() { } void ClipReader::error() { - _private = 0; _state = ClipError; } @@ -477,7 +494,7 @@ public: , _frameDelay(0) { } - bool readNextFrame(QImage &to, bool &hasAlpha, const QSize &size) { + bool readNextFrame() { if (_reader) _frameDelay = _reader->nextImageDelay(); if (_framesLeft < 1 && !jumpToStart()) { return false; @@ -587,6 +604,11 @@ public: } bool readNextFrame() { + if (_frameRead) { + av_frame_unref(_frame); + _frameRead = false; + } + int res; while (true) { if (_avpkt.size > 0) { // previous packet not finished @@ -638,6 +660,20 @@ public: } if (got_frame) { + int64 duration = av_frame_get_pkt_duration(_frame); + int64 framePts = (_frame->pkt_pts == AV_NOPTS_VALUE) ? _frame->pkt_dts : _frame->pkt_pts; + int64 frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; + _currentFrameDelay = _nextFrameDelay; + if (_frameMs + _currentFrameDelay < frameMs) { + _currentFrameDelay = int32(frameMs - _frameMs); + } + if (duration == AV_NOPTS_VALUE) { + _nextFrameDelay = 0; + } else { + _nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; + } + _frameMs = frameMs; + _hadFrame = _frameRead = true; return true; } @@ -700,20 +736,6 @@ public: } } - int64 duration = av_frame_get_pkt_duration(_frame); - int64 framePts = (_frame->pkt_pts == AV_NOPTS_VALUE) ? _frame->pkt_dts : _frame->pkt_pts; - int64 frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; - _currentFrameDelay = _nextFrameDelay; - if (_frameMs + _currentFrameDelay < frameMs) { - _currentFrameDelay = int32(frameMs - _frameMs); - } - if (duration == AV_NOPTS_VALUE) { - _nextFrameDelay = 0; - } else { - _nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; - } - _frameMs = frameMs; - av_frame_unref(_frame); return true; } @@ -792,6 +814,10 @@ public: } ~FFMpegReaderImplementation() { + if (_frameRead) { + av_frame_unref(_frame); + _frameRead = false; + } if (_ioContext) av_free(_ioContext); if (_codecContext) avcodec_close(_codecContext); if (_swsContext) sws_freeContext(_swsContext); diff --git a/Telegram/SourceFiles/gui/animation.h b/Telegram/SourceFiles/gui/animation.h index b3aa52afa0..9621f793c1 100644 --- a/Telegram/SourceFiles/gui/animation.h +++ b/Telegram/SourceFiles/gui/animation.h @@ -16,11 +16,11 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "types.h" +#include "basic_types.h" #include #include @@ -208,10 +208,10 @@ class AnimationCreator { public: AnimationCreator(AnimationImplementation *ptr) : _ptr(ptr) {} AnimationCreator(const AnimationCreator &other) : _ptr(other.create()) {} - AnimationImplementation *create() const { return exchange(_ptr); } + AnimationImplementation *create() const { return getPointerAndReset(_ptr); } ~AnimationCreator() { deleteAndMark(_ptr); } private: - AnimationCreator &operator=(const AnimationCreator &other); + AnimationCreator &operator=(const AnimationCreator &other) = delete; mutable AnimationImplementation *_ptr; }; class AnimationCallbacks { @@ -222,7 +222,7 @@ public: ~AnimationCallbacks() { deleteAndMark(_implementation); } private: AnimationCallbacks(const AnimationCallbacks &other); - AnimationCallbacks &operator=(const AnimationCallbacks &other); + AnimationCallbacks &operator=(const AnimationCallbacks &other) = delete; AnimationImplementation *_implementation; }; diff --git a/Telegram/SourceFiles/gui/boxshadow.cpp b/Telegram/SourceFiles/gui/boxshadow.cpp index 415978d7bc..c2df882146 100644 --- a/Telegram/SourceFiles/gui/boxshadow.cpp +++ b/Telegram/SourceFiles/gui/boxshadow.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" diff --git a/Telegram/SourceFiles/gui/boxshadow.h b/Telegram/SourceFiles/gui/boxshadow.h index 2d2b903ae7..bd4bb77650 100644 --- a/Telegram/SourceFiles/gui/boxshadow.h +++ b/Telegram/SourceFiles/gui/boxshadow.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/gui/button.cpp b/Telegram/SourceFiles/gui/button.cpp index 53f0e19507..767bac6254 100644 --- a/Telegram/SourceFiles/gui/button.cpp +++ b/Telegram/SourceFiles/gui/button.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "button.h" @@ -80,7 +80,6 @@ void Button::mouseReleaseEvent(QMouseEvent *e) { } void Button::setOver(bool over, ButtonStateChangeSource source) { -// LOG(("Set over: %1, by: %2 AT %3").arg(logBool(over)).arg(source).arg(dynamic_cast(this) ? dynamic_cast(this)->getText() : qsl("Unknown"))); if (over && !(_state & StateOver)) { int oldState = _state; _state |= StateOver; diff --git a/Telegram/SourceFiles/gui/button.h b/Telegram/SourceFiles/gui/button.h index 652fea137d..34577ade84 100644 --- a/Telegram/SourceFiles/gui/button.h +++ b/Telegram/SourceFiles/gui/button.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/gui/countryinput.cpp b/Telegram/SourceFiles/gui/countryinput.cpp index 5405737503..9f871f4036 100644 --- a/Telegram/SourceFiles/gui/countryinput.cpp +++ b/Telegram/SourceFiles/gui/countryinput.cpp @@ -16,10 +16,10 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" +#include "gui/style.h" #include "lang.h" #include "application.h" diff --git a/Telegram/SourceFiles/gui/countryinput.h b/Telegram/SourceFiles/gui/countryinput.h index 457c177e44..c861befe4c 100644 --- a/Telegram/SourceFiles/gui/countryinput.h +++ b/Telegram/SourceFiles/gui/countryinput.h @@ -16,11 +16,11 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "style.h" +#include "gui/style.h" #include "gui/flatinput.h" #include "gui/scrollarea.h" diff --git a/Telegram/SourceFiles/gui/emoji_config.cpp b/Telegram/SourceFiles/gui/emoji_config.cpp index 32e28e62b9..cf84ad2d9e 100644 --- a/Telegram/SourceFiles/gui/emoji_config.cpp +++ b/Telegram/SourceFiles/gui/emoji_config.cpp @@ -20,7 +20,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "gui/emoji_config.h" diff --git a/Telegram/SourceFiles/gui/emoji_config.h b/Telegram/SourceFiles/gui/emoji_config.h index e377374ba1..9f4fefb61b 100644 --- a/Telegram/SourceFiles/gui/emoji_config.h +++ b/Telegram/SourceFiles/gui/emoji_config.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -83,7 +83,7 @@ inline EmojiPtr emojiFromUrl(const QString &url) { return emojiFromKey(url.midRef(10).toULongLong(0, 16)); // skip emoji://e. } -inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int &len) { +inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int *plen = 0) { EmojiPtr emoji = 0; if (ch + 1 < e && ((ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) || (((ch->unicode() >= 0x30 && ch->unicode() < 0x3A) || ch->unicode() == 0x23 || ch->unicode() == 0x2A) && (ch + 1)->unicode() == 0x20E3))) { uint32 code = (ch->unicode() << 16) | (ch + 1)->unicode(); @@ -108,15 +108,15 @@ inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int &len) { } else if (ch + 2 < e && ((ch->unicode() >= 0x30 && ch->unicode() < 0x3A) || ch->unicode() == 0x23 || ch->unicode() == 0x2A) && (ch + 1)->unicode() == 0xFE0F && (ch + 2)->unicode() == 0x20E3) { uint32 code = (ch->unicode() << 16) | (ch + 2)->unicode(); emoji = emojiGet(code); - len = emoji->len + 1; + if (plen) *plen = emoji->len + 1; return emoji; } else if (ch < e) { emoji = emojiGet(ch->unicode()); - Q_ASSERT(emoji != TwoSymbolEmoji); + t_assert(emoji != TwoSymbolEmoji); } if (emoji) { - len = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0); + int32 len = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0); if (emoji->color && (ch + len + 1 < e && (ch + len)->isHighSurrogate() && (ch + len + 1)->isLowSurrogate())) { // color uint32 color = ((uint32((ch + len)->unicode()) << 16) | uint32((ch + len + 1)->unicode())); EmojiPtr col = emojiGet(emoji, color); @@ -128,8 +128,21 @@ inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int &len) { } } } + if (plen) *plen = len; + } + + return emoji; +} + +inline EmojiPtr emojiFromText(const QString &text, int32 *plen = 0) { + return text.isEmpty() ? EmojiPtr(0) : emojiFromText(text.constBegin(), text.constEnd(), plen); +} + +inline EmojiPtr emojiGetNoColor(EmojiPtr emoji) { + if (emoji && emoji->color && (emoji->color & 0xFFFF0000U) != 0xFFFF0000U) { + EmojiPtr result = emojiGet(emoji->code); + return (result == TwoSymbolEmoji) ? emojiGet(emoji->code, emoji->code2) : result; } - return emoji; } @@ -180,7 +193,7 @@ inline QString replaceEmojis(const QString &text, EntitiesInText &entities) { if (canFindEmoji) { emojiFind(ch, e, newEmojiEnd, emojiCode); } - + while (currentEntity < entitiesCount && ch >= emojiStart + entities[currentEntity].offset + entities[currentEntity].length) { ++currentEntity; } diff --git a/Telegram/SourceFiles/gui/filedialog.cpp b/Telegram/SourceFiles/gui/filedialog.cpp index 4727f81c79..50684f6611 100644 --- a/Telegram/SourceFiles/gui/filedialog.cpp +++ b/Telegram/SourceFiles/gui/filedialog.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "gui/filedialog.h" @@ -149,7 +149,7 @@ bool _filedialogGetFiles(QStringList &files, QByteArray &remoteContent, const QS cSetDialogLastPath(path); Local::writeUserSettings(); } - + if (res == QDialog::Accepted) { if (multipleFiles > 0) { files = dialog.selectedFiles(); @@ -157,9 +157,9 @@ bool _filedialogGetFiles(QStringList &files, QByteArray &remoteContent, const QS files = dialog.selectedFiles().mid(0, 1); } if (multipleFiles >= 0) { -#ifdef Q_OS_WIN +#if defined Q_OS_WIN && !defined Q_OS_WINRT remoteContent = dialog.selectedRemoteContent(); -#endif +#endif // Q_OS_WIN && !Q_OS_WINRT } return true; } diff --git a/Telegram/SourceFiles/gui/filedialog.h b/Telegram/SourceFiles/gui/filedialog.h index 58791f512c..33f3f13918 100644 --- a/Telegram/SourceFiles/gui/filedialog.h +++ b/Telegram/SourceFiles/gui/filedialog.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/gui/flatbutton.cpp b/Telegram/SourceFiles/gui/flatbutton.cpp index e41f13b355..e29de5f44f 100644 --- a/Telegram/SourceFiles/gui/flatbutton.cpp +++ b/Telegram/SourceFiles/gui/flatbutton.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "gui/flatbutton.h" @@ -311,21 +311,19 @@ void EmojiButton::paintEvent(QPaintEvent *e) { p.drawPixmap(t, App::sprite(), i); } - QRect inner(QPoint((width() - st::emojiCircle.width()) / 2, st::emojiCircleTop), st::emojiCircle); - int32 full = 5760; - int32 start = qRound(full * float64(ms % uint64(st::emojiCirclePeriod)) / st::emojiCirclePeriod), part = qRound(full / st::emojiCirclePart); - - p.setBrush(Qt::NoBrush); - p.setRenderHint(QPainter::HighQualityAntialiasing); - - p.setPen(QPen(st::emojiCircleFg->c, st::emojiCircleLine)); p.setOpacity(a_opacity.current() * _opacity); - p.drawEllipse(inner); - - p.setPen(QPen(st::white->c, st::emojiCircleLine)); - p.setOpacity(loading); - p.drawArc(inner, (full - start) % full, part); + p.setPen(QPen(st::emojiCircleFg, st::emojiCircleLine)); + p.setBrush(Qt::NoBrush); + p.setRenderHint(QPainter::HighQualityAntialiasing); + QRect inner(QPoint((width() - st::emojiCircle.width()) / 2, st::emojiCircleTop), st::emojiCircle); + if (loading > 0) { + int32 full = 5760; + int32 start = qRound(full * float64(ms % uint64(st::emojiCirclePeriod)) / st::emojiCirclePeriod), part = qRound(loading * full / st::emojiCirclePart); + p.drawArc(inner, start, full - part); + } else { + p.drawEllipse(inner); + } p.setRenderHint(QPainter::HighQualityAntialiasing, false); } diff --git a/Telegram/SourceFiles/gui/flatbutton.h b/Telegram/SourceFiles/gui/flatbutton.h index f07b0c4a25..5e1acd452c 100644 --- a/Telegram/SourceFiles/gui/flatbutton.h +++ b/Telegram/SourceFiles/gui/flatbutton.h @@ -16,14 +16,14 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once #include "gui/button.h" #include "gui/flatcheckbox.h" #include "gui/animation.h" -#include "style.h" +#include "gui/style.h" class FlatButton : public Button { Q_OBJECT diff --git a/Telegram/SourceFiles/gui/flatcheckbox.cpp b/Telegram/SourceFiles/gui/flatcheckbox.cpp index bace80fc7c..121c866afb 100644 --- a/Telegram/SourceFiles/gui/flatcheckbox.cpp +++ b/Telegram/SourceFiles/gui/flatcheckbox.cpp @@ -16,10 +16,10 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" +#include "gui/style.h" #include "lang.h" #include "flatcheckbox.h" diff --git a/Telegram/SourceFiles/gui/flatcheckbox.h b/Telegram/SourceFiles/gui/flatcheckbox.h index 560045fdfe..6e209e3e21 100644 --- a/Telegram/SourceFiles/gui/flatcheckbox.h +++ b/Telegram/SourceFiles/gui/flatcheckbox.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/gui/flatinput.cpp b/Telegram/SourceFiles/gui/flatinput.cpp index 5d0a25ace8..5c81e7db7b 100644 --- a/Telegram/SourceFiles/gui/flatinput.cpp +++ b/Telegram/SourceFiles/gui/flatinput.cpp @@ -16,10 +16,10 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" +#include "gui/style.h" #include "flatinput.h" #include "window.h" @@ -66,7 +66,7 @@ FlatInput::FlatInput(QWidget *parent, const style::flatInput &st, const QString , _notingBene(0) , _st(st) { resize(_st.width, _st.height); - + setFont(_st.font->f); setAlignment(_st.align); @@ -959,7 +959,7 @@ void InputArea::processDocumentContentsChange(int position, int charsAdded) { const QChar *ch = t.constData(), *e = ch + t.size(); for (; ch != e; ++ch, ++fp) { int32 emojiLen = 0; - emoji = emojiFromText(ch, e, emojiLen); + emoji = emojiFromText(ch, e, &emojiLen); if (emoji) { if (replacePosition >= 0) { emoji = 0; // replace tilde char format first @@ -1331,7 +1331,7 @@ InputField::InputField(QWidget *parent, const style::InputField &st, const QStri connect(&_inner, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool))); connect(&_inner, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool))); if (App::wnd()) connect(&_inner, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); - + setCursor(style::cur_text); if (!val.isEmpty()) { _inner.setPlainText(val); @@ -1412,7 +1412,7 @@ void InputField::paintEvent(QPaintEvent *e) { if (_st.iconSprite.pxWidth()) { p.drawSpriteLeft(_st.iconPosition, width(), _st.iconSprite); } - + bool drawPlaceholder = _placeholderVisible; if (_a_placeholderShift.animating()) { p.setOpacity(a_placeholderOpacity.current()); @@ -1425,11 +1425,11 @@ void InputField::paintEvent(QPaintEvent *e) { QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); r.moveLeft(r.left() + a_placeholderLeft.current()); if (rtl()) r.moveLeft(width() - r.left() - r.width()); - + p.setFont(_st.font); p.setPen(a_placeholderFg.current()); p.drawText(r, _placeholder, _st.placeholderAlign); - + p.restore(); } TWidget::paintEvent(e); @@ -1663,7 +1663,7 @@ void InputField::processDocumentContentsChange(int position, int charsAdded) { } int32 emojiLen = 0; - emoji = emojiFromText(ch, e, emojiLen); + emoji = emojiFromText(ch, e, &emojiLen); if (emoji) { if (replacePosition >= 0) { emoji = 0; // replace tilde char format first @@ -2028,7 +2028,7 @@ MaskedInputField::MaskedInputField(QWidget *parent, const style::InputField &st, setStyle(&_inputFieldStyle); QLineEdit::setTextMargins(0, 0, 0, 0); setContentsMargins(0, 0, 0, 0); - + setAttribute(Qt::WA_AcceptTouchEvents); _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); diff --git a/Telegram/SourceFiles/gui/flatinput.h b/Telegram/SourceFiles/gui/flatinput.h index 98b8d3be19..8c763b6d37 100644 --- a/Telegram/SourceFiles/gui/flatinput.h +++ b/Telegram/SourceFiles/gui/flatinput.h @@ -16,11 +16,11 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "style.h" +#include "gui/style.h" #include "animation.h" class FlatInput : public QLineEdit { diff --git a/Telegram/SourceFiles/gui/flatlabel.cpp b/Telegram/SourceFiles/gui/flatlabel.cpp index 42860323e4..34ac0e9e28 100644 --- a/Telegram/SourceFiles/gui/flatlabel.cpp +++ b/Telegram/SourceFiles/gui/flatlabel.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" @@ -46,7 +46,6 @@ void FlatLabel::setText(const QString &text) { void FlatLabel::setRichText(const QString &text) { textstyleSet(&_tst); - const char *t = text.toUtf8().constData(); _text.setRichText(_st.font, text, _labelOptions); int32 w = _st.width ? _st.width : _text.maxWidth(), h = _text.countHeight(w); textstyleRestore(); @@ -54,7 +53,14 @@ void FlatLabel::setRichText(const QString &text) { setMouseTracking(_text.hasLinks()); } -void FlatLabel::setLink(uint16 lnkIndex, const TextLinkPtr &lnk) { +void FlatLabel::resizeToWidth(int32 width) { + textstyleSet(&_tst); + int32 w = width, h = _text.countHeight(w); + textstyleRestore(); + resize(w, h); +} + +void FlatLabel::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { _text.setLink(lnkIndex, lnk); } @@ -66,30 +72,33 @@ void FlatLabel::mouseMoveEvent(QMouseEvent *e) { void FlatLabel::mousePressEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateHover(); - if (textlnkOver()) { - textlnkDown(textlnkOver()); - update(); - } + ClickHandler::pressed(); } void FlatLabel::mouseReleaseEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateHover(); - if (textlnkOver() && textlnkOver() == textlnkDown()) { - textlnkOver()->onClick(e->button()); + if (ClickHandlerPtr activated = ClickHandler::unpressed()) { + App::activateClickHandler(activated, e->button()); } - textlnkDown(TextLinkPtr()); +} + +void FlatLabel::enterEvent(QEvent *e) { + _lastMousePos = QCursor::pos(); + updateHover(); } void FlatLabel::leaveEvent(QEvent *e) { - if (_myLink) { - if (textlnkOver() == _myLink) { - textlnkOver(TextLinkPtr()); - update(); - } - _myLink = TextLinkPtr(); - setCursor(style::cur_default); - } + ClickHandler::clearActive(this); +} + +void FlatLabel::clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { + setCursor(active ? style::cur_pointer : style::cur_default); + update(); +} + +void FlatLabel::clickHandlerPressedChanged(const ClickHandlerPtr &action, bool active) { + update(); } void FlatLabel::updateLink() { @@ -99,17 +108,12 @@ void FlatLabel::updateLink() { void FlatLabel::updateHover() { QPoint m(mapFromGlobal(_lastMousePos)); - bool wasMy = (_myLink == textlnkOver()); + textstyleSet(&_tst); - _myLink = _text.link(m.x(), m.y(), width(), _st.align); + ClickHandlerPtr handler = _text.link(m.x(), m.y(), width(), _st.align); textstyleRestore(); - if (_myLink != textlnkOver()) { - if (wasMy || _myLink || rect().contains(m)) { - textlnkOver(_myLink); - } - setCursor(_myLink ? style::cur_pointer : style::cur_default); - update(); - } + + ClickHandler::setActive(handler, this); } void FlatLabel::setOpacity(float64 o) { diff --git a/Telegram/SourceFiles/gui/flatlabel.h b/Telegram/SourceFiles/gui/flatlabel.h index 28acee067c..842056b9e5 100644 --- a/Telegram/SourceFiles/gui/flatlabel.h +++ b/Telegram/SourceFiles/gui/flatlabel.h @@ -16,31 +16,38 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "style.h" +#include "gui/style.h" -class FlatLabel : public TWidget { +class FlatLabel : public TWidget, public ClickHandlerHost { Q_OBJECT public: FlatLabel(QWidget *parent, const QString &text, const style::flatLabel &st = st::labelDefFlat, const style::textStyle &tst = st::defaultTextStyle); - void paintEvent(QPaintEvent *e); - void mouseMoveEvent(QMouseEvent *e); - void mousePressEvent(QMouseEvent *e); - void mouseReleaseEvent(QMouseEvent *e); - void leaveEvent(QEvent *e); + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; void updateLink(); void setOpacity(float64 o); void setText(const QString &text); void setRichText(const QString &text); - void setLink(uint16 lnkIndex, const TextLinkPtr &lnk); + void resizeToWidth(int32 width); + + void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); + + // ClickHandlerHost interface + void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) override; + void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) override; private: @@ -52,6 +59,5 @@ private: float64 _opacity; QPoint _lastMousePos; - TextLinkPtr _myLink; }; diff --git a/Telegram/SourceFiles/gui/flattextarea.cpp b/Telegram/SourceFiles/gui/flattextarea.cpp index 4f27183e77..f406ddea3c 100644 --- a/Telegram/SourceFiles/gui/flattextarea.cpp +++ b/Telegram/SourceFiles/gui/flattextarea.cpp @@ -16,12 +16,12 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" - #include "flattextarea.h" + +#include "gui/style.h" #include "window.h" FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &pholder, const QString &v) : QTextEdit(parent) @@ -298,10 +298,10 @@ QString FlatTextarea::getInlineBotQuery(UserData *&inlineBot, QString &inlineBot inlineBot = 0; } } else { - inlineBot = InlineBotLookingUpData; + inlineBot = LookingUpInlineBot; } } - if (inlineBot == InlineBotLookingUpData) return QString(); + if (inlineBot == LookingUpInlineBot) return QString(); if (inlineBot && (!inlineBot->botInfo || inlineBot->botInfo->inlinePlaceholder.isEmpty())) { inlineBot = 0; @@ -392,7 +392,7 @@ void FlatTextarea::onMentionHashtagOrBotCommandInsert(QString str) { QString t(fr.text()); for (int i = pos - p; i > 0; --i) { if (t.at(i - 1) == '@' || t.at(i - 1) == '#' || t.at(i - 1) == '/') { - if ((i == pos - p || t.at(i).isLetter() || t.at(i - 1) == '#') && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) { + if ((i == pos - p || (t.at(i - 1) == '/' ? t.at(i).isLetterOrNumber() : t.at(i).isLetter()) || t.at(i - 1) == '#') && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) { c.setPosition(p + i - 1, QTextCursor::MoveAnchor); int till = p + i; for (; (till < e) && (till - p - i + 1 < str.size()); ++till) { @@ -738,7 +738,7 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) { const QChar *ch = t.constData(), *e = ch + t.size(); for (; ch != e; ++ch, ++fp) { int32 emojiLen = 0; - emoji = emojiFromText(ch, e, emojiLen); + emoji = emojiFromText(ch, e, &emojiLen); if (emoji) { if (replacePosition >= 0) { emoji = 0; // replace tilde char format first diff --git a/Telegram/SourceFiles/gui/flattextarea.h b/Telegram/SourceFiles/gui/flattextarea.h index 14b9fa10dc..17f067a71f 100644 --- a/Telegram/SourceFiles/gui/flattextarea.h +++ b/Telegram/SourceFiles/gui/flattextarea.h @@ -16,15 +16,17 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once #include -#include "style.h" +#include "gui/style.h" #include "animation.h" class UserData; +static UserData * const LookingUpInlineBot = SharedMemoryLocation(); + class FlatTextarea : public QTextEdit { Q_OBJECT T_WIDGET diff --git a/Telegram/SourceFiles/gui/images.cpp b/Telegram/SourceFiles/gui/images.cpp index f8e3b18718..296409424a 100644 --- a/Telegram/SourceFiles/gui/images.cpp +++ b/Telegram/SourceFiles/gui/images.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "gui/images.h" @@ -47,6 +47,7 @@ namespace { static const uint64 ColoredCacheSkip = 0x2000000000000000LLU; static const uint64 BlurredColoredCacheSkip = 0x3000000000000000LLU; static const uint64 RoundedCacheSkip = 0x4000000000000000LLU; + static const uint64 CircledCacheSkip = 0x5000000000000000LLU; } StorageImageLocation StorageImageLocation::Null; @@ -106,7 +107,7 @@ const QPixmap &Image::pix(int32 w, int32 h) const { uint64 k = (uint64(w) << 32) | uint64(h); Sizes::const_iterator i = _sizesCache.constFind(k); if (i == _sizesCache.cend()) { - QPixmap p(pixNoCache(w, h, true)); + QPixmap p(pixNoCache(w, h, ImagePixSmooth)); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { @@ -128,7 +129,29 @@ const QPixmap &Image::pixRounded(int32 w, int32 h) const { uint64 k = RoundedCacheSkip | (uint64(w) << 32) | uint64(h); Sizes::const_iterator i = _sizesCache.constFind(k); if (i == _sizesCache.cend()) { - QPixmap p(pixNoCache(w, h, true, false, true)); + QPixmap p(pixNoCache(w, h, ImagePixSmooth | ImagePixRounded)); + if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); + i = _sizesCache.insert(k, p); + if (!p.isNull()) { + globalAcquiredSize += int64(p.width()) * p.height() * 4; + } + } + return i.value(); +} + +const QPixmap &Image::pixCircled(int32 w, int32 h) const { + checkload(); + + if (w <= 0 || !width() || !height()) { + w = width(); + } else if (cRetina()) { + w *= cIntRetinaFactor(); + h *= cIntRetinaFactor(); + } + uint64 k = CircledCacheSkip | (uint64(w) << 32) | uint64(h); + Sizes::const_iterator i = _sizesCache.constFind(k); + if (i == _sizesCache.cend()) { + QPixmap p(pixNoCache(w, h, ImagePixSmooth | ImagePixCircled)); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { @@ -150,7 +173,7 @@ const QPixmap &Image::pixBlurred(int32 w, int32 h) const { uint64 k = BlurredCacheSkip | (uint64(w) << 32) | uint64(h); Sizes::const_iterator i = _sizesCache.constFind(k); if (i == _sizesCache.cend()) { - QPixmap p(pixNoCache(w, h, true, true)); + QPixmap p(pixNoCache(w, h, ImagePixSmooth | ImagePixBlurred)); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { @@ -219,7 +242,7 @@ const QPixmap &Image::pixSingle(int32 w, int32 h, int32 outerw, int32 outerh) co if (i != _sizesCache.cend()) { globalAcquiredSize -= int64(i->width()) * i->height() * 4; } - QPixmap p(pixNoCache(w, h, true, false, true, outerw, outerh)); + QPixmap p(pixNoCache(w, h, ImagePixSmooth | ImagePixRounded, outerw, outerh)); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { @@ -229,7 +252,7 @@ const QPixmap &Image::pixSingle(int32 w, int32 h, int32 outerw, int32 outerh) co return i.value(); } -const QPixmap &Image::pixBlurredSingle(int32 w, int32 h, int32 outerw, int32 outerh) const { +const QPixmap &Image::pixBlurredSingle(int w, int h, int32 outerw, int32 outerh) const { checkload(); if (w <= 0 || !width() || !height()) { @@ -244,7 +267,7 @@ const QPixmap &Image::pixBlurredSingle(int32 w, int32 h, int32 outerw, int32 out if (i != _sizesCache.cend()) { globalAcquiredSize -= int64(i->width()) * i->height() * 4; } - QPixmap p(pixNoCache(w, h, true, true, true, outerw, outerh)); + QPixmap p(pixNoCache(w, h, ImagePixSmooth | ImagePixBlurred | ImagePixRounded, outerw, outerh)); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { @@ -264,6 +287,7 @@ QImage imageBlur(QImage img) { QImage::Format fmt = img.format(); if (fmt != QImage::Format_RGB32 && fmt != QImage::Format_ARGB32_Premultiplied) { img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); + t_assert(!img.isNull()); } uchar *pix = img.bits(); @@ -287,6 +311,8 @@ QImage imageBlur(QImage img) { QImage was = img; img = imgsmall; imgsmall = QImage(); + t_assert(!img.isNull()); + pix = img.bits(); if (!pix) return was; } @@ -365,20 +391,63 @@ yi += stride; #undef update } - + delete[] rgb; } } return img; } -void imageRound(QImage &img) { +const QPixmap &circleMask(int width, int height) { + t_assert(Global::started()); + + uint64 key = uint64(uint32(width)) << 32 | uint64(uint32(height)); + + Global::CircleMasksMap &masks(Global::RefCircleMasks()); + auto i = masks.constFind(key); + if (i == masks.cend()) { + QImage mask(width, height, QImage::Format_ARGB32_Premultiplied); + mask.fill(st::transparent); + { + Painter p(&mask); + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + p.setBrush(st::white); + p.setPen(Qt::NoPen); + p.drawEllipse(0, 0, width, height); + } + mask.setDevicePixelRatio(cRetinaFactor()); + i = masks.insert(key, QPixmap::fromImage(mask)); + } + return i.value(); +} + +void imageCircle(QImage &img) { + t_assert(!img.isNull()); + img.setDevicePixelRatio(cRetinaFactor()); img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); + t_assert(!img.isNull()); + + QPixmap mask = circleMask(img.width(), img.height()); + Painter p(&img); + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + p.drawPixmap(0, 0, mask); +} + +void imageRound(QImage &img) { + t_assert(!img.isNull()); + + img.setDevicePixelRatio(cRetinaFactor()); + img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); + t_assert(!img.isNull()); QImage **masks = App::cornersMask(); int32 w = masks[0]->width(), h = masks[0]->height(); int32 tw = img.width(), th = img.height(); + if (tw < 2 * w || th < 2 * h) { + return; + } uchar *bits = img.bits(); const uchar *c0 = masks[0]->constBits(), *c1 = masks[1]->constBits(), *c2 = masks[2]->constBits(), *c3 = masks[3]->constBits(); @@ -426,13 +495,19 @@ QImage imageColored(const style::color &add, QImage img) { return img; } -QPixmap imagePix(QImage img, int32 w, int32 h, bool smooth, bool blurred, bool rounded, int32 outerw, int32 outerh) { - if (blurred) img = imageBlur(img); +QPixmap imagePix(QImage img, int32 w, int32 h, ImagePixOptions options, int32 outerw, int32 outerh) { + t_assert(!img.isNull()); + if (options.testFlag(ImagePixBlurred)) { + img = imageBlur(img); + t_assert(!img.isNull()); + } if (w <= 0 || (w == img.width() && (h <= 0 || h == img.height()))) { } else if (h <= 0) { - img = img.scaledToWidth(w, smooth ? Qt::SmoothTransformation : Qt::FastTransformation); + img = img.scaledToWidth(w, options.testFlag(ImagePixSmooth) ? Qt::SmoothTransformation : Qt::FastTransformation); + t_assert(!img.isNull()); } else { - img = img.scaled(w, h, Qt::IgnoreAspectRatio, smooth ? Qt::SmoothTransformation : Qt::FastTransformation); + img = img.scaled(w, h, Qt::IgnoreAspectRatio, options.testFlag(ImagePixSmooth) ? Qt::SmoothTransformation : Qt::FastTransformation); + t_assert(!img.isNull()); } if (outerw > 0 && outerh > 0) { outerw *= cIntRetinaFactor(); @@ -444,22 +519,35 @@ QPixmap imagePix(QImage img, int32 w, int32 h, bool smooth, bool blurred, bool r { QPainter p(&result); if (w < outerw || h < outerh) { - p.fillRect(0, 0, result.width(), result.height(), st::black->b); + p.fillRect(0, 0, result.width(), result.height(), st::black); } p.drawImage((result.width() - img.width()) / (2 * cIntRetinaFactor()), (result.height() - img.height()) / (2 * cIntRetinaFactor()), img); } img = result; + t_assert(!img.isNull()); } } - if (rounded) imageRound(img); + if (options.testFlag(ImagePixCircled)) { + imageCircle(img); + t_assert(!img.isNull()); + } else if (options.testFlag(ImagePixRounded)) { + imageRound(img); + t_assert(!img.isNull()); + } img.setDevicePixelRatio(cRetinaFactor()); return QPixmap::fromImage(img, Qt::ColorOnly); } -QPixmap Image::pixNoCache(int32 w, int32 h, bool smooth, bool blurred, bool rounded, int32 outerw, int32 outerh) const { +QPixmap Image::pixNoCache(int w, int h, ImagePixOptions options, int outerw, int outerh) const { if (!loading()) const_cast(this)->load(); restore(); - if (_data.isNull()) return blank()->pix(); + + if (_data.isNull()) { + if (h <= 0 && height() > 0) { + h = qRound(width() * w / float64(height())); + } + return blank()->pixNoCache(w, h, options, outerw, outerh); + } if (isNull() && outerw > 0 && outerh > 0) { outerw *= cIntRetinaFactor(); @@ -470,13 +558,26 @@ QPixmap Image::pixNoCache(int32 w, int32 h, bool smooth, bool blurred, bool roun { QPainter p(&result); - p.fillRect(0, 0, result.width(), result.height(), st::black); + if (w < outerw) { + p.fillRect(0, 0, (outerw - w) / 2, result.height(), st::black); + p.fillRect(((outerw - w) / 2) + w, 0, result.width() - (((outerw - w) / 2) + w), result.height(), st::black); + } + if (h < outerh) { + p.fillRect(qMax(0, (outerw - w) / 2), 0, qMin(result.width(), w), (outerh - h) / 2, st::black); + p.fillRect(qMax(0, (outerw - w) / 2), ((outerh - h) / 2) + h, qMin(result.width(), w), result.height() - (((outerh - h) / 2) + h), st::black); + } + p.fillRect(qMax(0, (outerw - w) / 2), qMax(0, (outerh - h) / 2), qMin(result.width(), w), qMin(result.height(), h), st::white); } - if (rounded) imageRound(result); + if (options.testFlag(ImagePixCircled)) { + imageCircle(result); + } else if (options.testFlag(ImagePixRounded)) { + imageRound(result); + } return QPixmap::fromImage(result, Qt::ColorOnly); } - return imagePix(_data.toImage(), w, h, smooth, blurred, rounded, outerw, outerh); + + return imagePix(_data.toImage(), w, h, options, outerw, outerh); } QPixmap Image::pixColoredNoCache(const style::color &add, int32 w, int32 h, bool smooth) const { @@ -774,11 +875,11 @@ StorageImage::StorageImage(const StorageImageLocation &location, QByteArray &byt } } -int32 StorageImage::width() const { +int32 StorageImage::countWidth() const { return _location.width(); } -int32 StorageImage::height() const { +int32 StorageImage::countHeight() const { return _location.height(); } @@ -904,11 +1005,11 @@ StorageImage *getImage(const StorageImageLocation &location, const QByteArray &b WebImage::WebImage(const QString &url) : _url(url), _size(0), _width(0), _height(0) { } -int32 WebImage::width() const { +int32 WebImage::countWidth() const { return _width; } -int32 WebImage::height() const { +int32 WebImage::countHeight() const { return _height; } @@ -944,7 +1045,7 @@ FileLocation::FileLocation(StorageFileType type, const QString &name) : type(typ qint64 s = f.size(); if (s > INT_MAX) { fname = QString(); - _bookmark.reset(0); + _bookmark.clear(); size = 0; type = StorageFileUnknown; } else { @@ -953,7 +1054,7 @@ FileLocation::FileLocation(StorageFileType type, const QString &name) : type(typ } } else { fname = QString(); - _bookmark.reset(0); + _bookmark.clear(); size = 0; type = StorageFileUnknown; } @@ -965,7 +1066,7 @@ bool FileLocation::check() const { ReadAccessEnabler enabler(_bookmark); if (enabler.failed()) { - const_cast(this)->_bookmark.reset(0); + const_cast(this)->_bookmark.clear(); } QFileInfo f(name()); @@ -986,11 +1087,7 @@ QByteArray FileLocation::bookmark() const { } void FileLocation::setBookmark(const QByteArray &bm) { - if (bm.isEmpty()) { - _bookmark.reset(0); - } else { - _bookmark.reset(new PsFileBookmark(bm)); - } + _bookmark.reset(bm.isEmpty() ? nullptr : new PsFileBookmark(bm)); } bool FileLocation::accessEnable() const { diff --git a/Telegram/SourceFiles/gui/images.h b/Telegram/SourceFiles/gui/images.h index 5ef9d93282..0622dca277 100644 --- a/Telegram/SourceFiles/gui/images.h +++ b/Telegram/SourceFiles/gui/images.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -107,7 +107,15 @@ inline bool operator!=(const StorageImageLocation &a, const StorageImageLocation return !(a == b); } -QPixmap imagePix(QImage img, int32 w, int32 h, bool smooth, bool blurred, bool rounded, int32 outerw, int32 outerh); +enum ImagePixOption { + ImagePixSmooth = 0x01, + ImagePixBlurred = 0x02, + ImagePixRounded = 0x04, + ImagePixCircled = 0x08, +}; +Q_DECLARE_FLAGS(ImagePixOptions, ImagePixOption); +Q_DECLARE_OPERATORS_FOR_FLAGS(ImagePixOptions); +QPixmap imagePix(QImage img, int w, int h, ImagePixOptions options, int outerw, int outerh); class DelayedStorageImage; @@ -145,23 +153,22 @@ public: const QPixmap &pix(int32 w = 0, int32 h = 0) const; const QPixmap &pixRounded(int32 w = 0, int32 h = 0) const; + const QPixmap &pixCircled(int32 w = 0, int32 h = 0) const; const QPixmap &pixBlurred(int32 w = 0, int32 h = 0) const; const QPixmap &pixColored(const style::color &add, int32 w = 0, int32 h = 0) const; const QPixmap &pixBlurredColored(const style::color &add, int32 w = 0, int32 h = 0) const; const QPixmap &pixSingle(int32 w, int32 h, int32 outerw, int32 outerh) const; const QPixmap &pixBlurredSingle(int32 w, int32 h, int32 outerw, int32 outerh) const; - QPixmap pixNoCache(int32 w = 0, int32 h = 0, bool smooth = false, bool blurred = false, bool rounded = false, int32 outerw = -1, int32 outerh = -1) const; + QPixmap pixNoCache(int w = 0, int h = 0, ImagePixOptions options = 0, int outerw = -1, int outerh = -1) const; QPixmap pixColoredNoCache(const style::color &add, int32 w = 0, int32 h = 0, bool smooth = false) const; QPixmap pixBlurredColoredNoCache(const style::color &add, int32 w, int32 h = 0) const; - virtual int32 width() const { - restore(); - return _data.width(); + int32 width() const { + return qMax(countWidth(), 1); } - virtual int32 height() const { - restore(); - return _data.height(); + int32 height() const { + return qMax(countHeight(), 1); } virtual void load(bool loadFirst = false, bool prior = true) { @@ -203,6 +210,16 @@ protected: } void invalidateSizeCache() const; + virtual int32 countWidth() const { + restore(); + return _data.width(); + } + + virtual int32 countHeight() const { + restore(); + return _data.height(); + } + mutable QByteArray _saved, _format; mutable bool _forgot; mutable QPixmap _data; @@ -283,9 +300,6 @@ public: StorageImage(const StorageImageLocation &location, int32 size = 0); StorageImage(const StorageImageLocation &location, QByteArray &bytes); - int32 width() const; - int32 height() const; - virtual void setInformation(int32 size, int32 width, int32 height); virtual FileLoader *createLoader(LoadFromCloudSetting fromCloud, bool autoLoading); @@ -297,6 +311,9 @@ protected: StorageImageLocation _location; int32 _size; + virtual int32 countWidth() const; + virtual int32 countHeight() const; + }; class DelayedStorageImage : public StorageImage { @@ -341,12 +358,14 @@ public: WebImage(const QString &url); - int32 width() const; - int32 height() const; - virtual void setInformation(int32 size, int32 width, int32 height); virtual FileLoader *createLoader(LoadFromCloudSetting fromCloud, bool autoLoading); +protected: + + virtual int32 countWidth() const; + virtual int32 countHeight() const; + private: QString _url; int32 _size, _width, _height; diff --git a/Telegram/SourceFiles/gui/popupmenu.cpp b/Telegram/SourceFiles/gui/popupmenu.cpp index 21fefa3e1d..d78746c874 100644 --- a/Telegram/SourceFiles/gui/popupmenu.cpp +++ b/Telegram/SourceFiles/gui/popupmenu.cpp @@ -13,7 +13,7 @@ GNU General Public License for more details. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE - Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org + Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" @@ -81,7 +81,7 @@ void PopupMenu::init() { QAction *PopupMenu::addAction(const QString &text, const QObject *receiver, const char* member) { QAction *a = new QAction(text, this); - connect(a, SIGNAL(triggered(bool)), receiver, member); + connect(a, SIGNAL(triggered(bool)), receiver, member, Qt::QueuedConnection); return addAction(a); } @@ -103,6 +103,12 @@ QAction *PopupMenu::addAction(QAction *a) { return a; } +QAction *PopupMenu::addSeparator() { + QAction *separator = new QAction(this); + separator->setSeparator(true); + return addAction(separator); +} + int32 PopupMenu::processAction(QAction *a, int32 index, int32 w) { if (a->isSeparator() || a->text().isEmpty()) { _texts[index] = _shortcutTexts[index] = QString(); @@ -464,7 +470,7 @@ void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, PressSource source) _parent = parent; QPoint w = p - QPoint(0, _padding.top()); - QRect r = App::app() ? App::app()->desktop()->screenGeometry(p) : QDesktopWidget().screenGeometry(p); + QRect r = Sandbox::screenGeometry(p); if (rtl()) { if (w.x() - width() < r.x() - _padding.left()) { if (_parent && w.x() + _parent->width() - _padding.left() - _padding.right() + width() - _padding.right() <= r.x() + r.width()) { @@ -520,3 +526,165 @@ PopupMenu::~PopupMenu() { } #endif } + +PopupTooltip *PopupTooltipInstance = 0; + +AbstractTooltipShower::~AbstractTooltipShower() { + if (PopupTooltipInstance && PopupTooltipInstance->_shower == this) { + PopupTooltipInstance->_shower = 0; + } +} + +PopupTooltip::PopupTooltip() : TWidget(0) +, _shower(0) +, _st(0) { + PopupTooltipInstance = this; + + setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::ToolTip | Qt::NoDropShadowWindowHint); + setAttribute(Qt::WA_NoSystemBackground, true); + + _showTimer.setSingleShot(true); + connect(&_showTimer, SIGNAL(timeout()), this, SLOT(onShow())); + + connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); +} + +void PopupTooltip::onShow() { + if (_shower) { + QString text = (App::wnd() && App::wnd()->isActive(false)) ? _shower->tooltipText() : QString(); + if (text.isEmpty()) { + Hide(); + } else { + PopupTooltipInstance->popup(_shower->tooltipPos(), text, _shower->tooltipSt()); + } + } +} + +void PopupTooltip::onWndActiveChanged() { + if (!App::wnd() || !App::wnd()->windowHandle() || !App::wnd()->windowHandle()->isActive()) { + PopupTooltip::Hide(); + } +} + +bool PopupTooltip::eventFilter(QObject *o, QEvent *e) { + if (e->type() == QEvent::Leave) { + _hideByLeaveTimer.start(10); + } else if (e->type() == QEvent::Enter) { + _hideByLeaveTimer.stop(); + } else if (e->type() == QEvent::MouseMove) { + if ((QCursor::pos() - _point).manhattanLength() > QApplication::startDragDistance()) { + Hide(); + } + } + return TWidget::eventFilter(o, e); +} + +void PopupTooltip::onHideByLeave() { + Hide(); +} + +PopupTooltip::~PopupTooltip() { + if (PopupTooltipInstance == this) { + PopupTooltipInstance = 0; + } +} + +void PopupTooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *st) { + if (!_hideByLeaveTimer.isSingleShot()) { + _hideByLeaveTimer.setSingleShot(true); + connect(&_hideByLeaveTimer, SIGNAL(timeout()), this, SLOT(onHideByLeave())); + + Sandbox::installEventFilter(this); + } + + _point = m; + _st = st; + _text = Text(_st->textFont, text, _textPlainOptions, _st->widthMax, true); + + int32 addw = 2 * st::lineWidth + _st->textPadding.left() + _st->textPadding.right(); + int32 addh = 2 * st::lineWidth + _st->textPadding.top() + _st->textPadding.bottom(); + + // count tooltip size + QSize s(addw + _text.maxWidth(), addh + _text.minHeight()); + if (s.width() > _st->widthMax) { + s.setWidth(addw + _text.countWidth(_st->widthMax - addw)); + s.setHeight(addh + _text.countHeight(s.width() - addw)); + } + int32 maxh = addh + (_st->linesMax * _st->textFont->height); + if (s.height() > maxh) { + s.setHeight(maxh); + } + + // count tooltip position + QPoint p(m + _st->shift); + if (rtl()) { + p.setX(m.x() - s.width() - _st->shift.x()); + } + if (s.width() < 2 * _st->shift.x()) { + p.setX(m.x() - (s.width() / 2)); + } + + // adjust tooltip position + QRect r(QApplication::desktop()->screenGeometry(m)); + if (r.x() + r.width() - _st->skip < p.x() + s.width() && p.x() + s.width() > m.x()) { + p.setX(qMax(r.x() + r.width() - int32(_st->skip) - s.width(), m.x() - s.width())); + } + if (r.x() + _st->skip > p.x() && p.x() < m.x()) { + p.setX(qMin(m.x(), r.x() + int32(_st->skip))); + } + if (r.y() + r.height() - _st->skip < p.y() + s.height()) { + p.setY(m.y() - s.height() - _st->skip); + } + if (r.y() > p.x()) { + p.setY(qMin(m.y() + _st->shift.y(), r.y() + r.height() - s.height())); + } + + setGeometry(QRect(p, s)); + + _hideByLeaveTimer.stop(); + show(); +} + +void PopupTooltip::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.fillRect(rect(), _st->textBg); + + p.fillRect(QRect(0, 0, width(), st::lineWidth), _st->textBorder); + p.fillRect(QRect(0, height() - st::lineWidth, width(), st::lineWidth), _st->textBorder); + p.fillRect(QRect(0, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder); + p.fillRect(QRect(width() - st::lineWidth, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder); + + int32 lines = qFloor((height() - 2 * st::lineWidth - _st->textPadding.top() - _st->textPadding.bottom()) / _st->textFont->height); + + p.setPen(_st->textFg); + _text.drawElided(p, st::lineWidth + _st->textPadding.left(), st::lineWidth + _st->textPadding.top(), width() - 2 * st::lineWidth - _st->textPadding.left() - _st->textPadding.right(), lines); +} + +void PopupTooltip::hideEvent(QHideEvent *e) { + if (PopupTooltipInstance == this) { + Hide(); + } +} + +void PopupTooltip::Show(int32 delay, const AbstractTooltipShower *shower) { + if (!PopupTooltipInstance) { + new PopupTooltip(); + } + PopupTooltipInstance->_shower = shower; + if (delay >= 0) { + PopupTooltipInstance->_showTimer.start(delay); + } else { + PopupTooltipInstance->onShow(); + } +} + +void PopupTooltip::Hide() { + if (PopupTooltip *instance = PopupTooltipInstance) { + PopupTooltipInstance = 0; + instance->_showTimer.stop(); + instance->_hideByLeaveTimer.stop(); + instance->hide(); + instance->deleteLater(); + } +} diff --git a/Telegram/SourceFiles/gui/popupmenu.h b/Telegram/SourceFiles/gui/popupmenu.h index 121984e830..64ff31fb6b 100644 --- a/Telegram/SourceFiles/gui/popupmenu.h +++ b/Telegram/SourceFiles/gui/popupmenu.h @@ -13,10 +13,12 @@ GNU General Public License for more details. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE - Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org + Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +#include "text.h" + class PopupMenu : public TWidget { Q_OBJECT @@ -26,6 +28,7 @@ public: PopupMenu(QMenu *menu, const style::PopupMenu &st = st::defaultPopupMenu); QAction *addAction(const QString &text, const QObject *receiver, const char* member); QAction *addAction(QAction *a); + QAction *addSeparator(); void resetActions(); typedef QVector Actions; @@ -105,3 +108,55 @@ private: bool _deleteOnHide, _triggering, _deleteLater; }; + +class AbstractTooltipShower { +public: + virtual QString tooltipText() const = 0; + virtual QPoint tooltipPos() const = 0; + virtual const style::Tooltip *tooltipSt() const { + return &st::defaultTooltip; + } + virtual ~AbstractTooltipShower(); +}; + +class PopupTooltip : public TWidget { + Q_OBJECT + +public: + + bool eventFilter(QObject *o, QEvent *e); + + static void Show(int32 delay, const AbstractTooltipShower *shower); + static void Hide(); + + ~PopupTooltip(); + +public slots: + + void onShow(); + void onWndActiveChanged(); + void onHideByLeave(); + +protected: + + void paintEvent(QPaintEvent *e); + void hideEvent(QHideEvent *e); + +private: + + PopupTooltip(); + + void popup(const QPoint &p, const QString &text, const style::Tooltip *st); + + friend class AbstractTooltipShower; + const AbstractTooltipShower *_shower; + QTimer _showTimer; + + Text _text; + QPoint _point; + + const style::Tooltip *_st; + + QTimer _hideByLeaveTimer; + +}; diff --git a/Telegram/SourceFiles/gui/scrollarea.cpp b/Telegram/SourceFiles/gui/scrollarea.cpp index 9fdc86a76f..0be3378d1b 100644 --- a/Telegram/SourceFiles/gui/scrollarea.cpp +++ b/Telegram/SourceFiles/gui/scrollarea.cpp @@ -16,10 +16,10 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" +#include "gui/style.h" #include "gui/scrollarea.h" diff --git a/Telegram/SourceFiles/gui/scrollarea.h b/Telegram/SourceFiles/gui/scrollarea.h index 76add3f2ba..b26665b34a 100644 --- a/Telegram/SourceFiles/gui/scrollarea.h +++ b/Telegram/SourceFiles/gui/scrollarea.h @@ -16,12 +16,12 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once #include -#include "style.h" +#include "gui/style.h" enum TouchScrollState { TouchScrollManual, // Scrolling manually with the finger on the screen diff --git a/Telegram/SourceFiles/style.h b/Telegram/SourceFiles/gui/style.h similarity index 93% rename from Telegram/SourceFiles/style.h rename to Telegram/SourceFiles/gui/style.h index bcbbb5c08b..f9fcf062a0 100644 --- a/Telegram/SourceFiles/style.h +++ b/Telegram/SourceFiles/gui/style.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/gui/style_core.cpp b/Telegram/SourceFiles/gui/style_core.cpp index 3537f1e92c..d7c99ccd4b 100644 --- a/Telegram/SourceFiles/gui/style_core.cpp +++ b/Telegram/SourceFiles/gui/style_core.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" diff --git a/Telegram/SourceFiles/gui/style_core.h b/Telegram/SourceFiles/gui/style_core.h index 8642a701ac..b36cfe5a90 100644 --- a/Telegram/SourceFiles/gui/style_core.h +++ b/Telegram/SourceFiles/gui/style_core.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp index fb8401019e..c6ab96669e 100644 --- a/Telegram/SourceFiles/gui/text.cpp +++ b/Telegram/SourceFiles/gui/text.cpp @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "text.h" @@ -36,7 +36,7 @@ namespace { const QRegularExpression _reMailName(qsl("[a-zA-Z\\-_\\.0-9]{1,256}$")); const QRegularExpression _reMailStart(qsl("^[a-zA-Z\\-_\\.0-9]{1,256}\\@")); const QRegularExpression _reHashtag(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])#[\\w]{2,64}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption); - const QRegularExpression _reMention(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])@[A-Za-z_0-9]{3,32}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption); + const QRegularExpression _reMention(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])@[A-Za-z_0-9]{1,32}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption); const QRegularExpression _reBotCommand(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])/[A-Za-z_0-9]{1,64}(@[A-Za-z_0-9]{5,32})?([\\W]|$)")); const QRegularExpression _rePre(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10])(````?)[\\s\\S]+?(````?)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)"), QRegularExpression::UseUnicodePropertiesOption); const QRegularExpression _reCode(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10])(`)[^\\n]+?(`)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)"), QRegularExpression::UseUnicodePropertiesOption); @@ -44,8 +44,6 @@ namespace { const style::textStyle *_textStyle = 0; - TextLinkPtr _overLnk, _downLnk, _zeroLnk; - void _initDefault() { _textStyle = &st::defaultTextStyle; } @@ -59,6 +57,47 @@ namespace { } } +ClickHandlerHost::~ClickHandlerHost() { + ClickHandler::hostDestroyed(this); +} + +NeverFreedPointer ClickHandler::_active; +NeverFreedPointer ClickHandler::_pressed; +ClickHandlerHost *ClickHandler::_activeHost = nullptr; +ClickHandlerHost *ClickHandler::_pressedHost = nullptr; + +bool ClickHandler::setActive(const ClickHandlerPtr &p, ClickHandlerHost *host) { + if ((_active && (*_active == p)) || (!_active && !p)) { + return false; + } + + // emit clickHandlerActiveChanged only when there is no + // other pressed click handler currently, if there is + // this method will be called when it is unpressed + if (_active && *_active) { + bool emitClickHandlerActiveChanged = (!_pressed || !*_pressed || *_pressed == *_active); + ClickHandlerPtr wasactive = *_active; + (*_active).clear(); + if (_activeHost) { + if (emitClickHandlerActiveChanged) { + _activeHost->clickHandlerActiveChanged(wasactive, false); + } + _activeHost = nullptr; + } + } + if (p) { + _active.makeIfNull(); + *_active = p; + if ((_activeHost = host)) { + bool emitClickHandlerActiveChanged = (!_pressed || !*_pressed || *_pressed == *_active); + if (emitClickHandlerActiveChanged) { + _activeHost->clickHandlerActiveChanged(*_active, true); + } + } + } + return true; +} + const QRegularExpression &reDomain() { return _reDomain; } @@ -87,26 +126,6 @@ void textstyleSet(const style::textStyle *style) { _textStyle = style ? style : &st::defaultTextStyle; } -void textlnkOver(const TextLinkPtr &lnk) { - _overLnk = lnk; -} - -const TextLinkPtr &textlnkOver() { - return _overLnk; -} - -void textlnkDown(const TextLinkPtr &lnk) { - _downLnk = lnk; -} - -const TextLinkPtr &textlnkDown() { - return _downLnk; -} - -bool textlnkDrawOver(const TextLinkPtr &lnk) { - return (_overLnk == lnk) && (!_downLnk || _downLnk == lnk); -} - QString textOneLine(const QString &text, bool trim, bool rich) { QString result(text); const QChar *s = text.unicode(), *ch = s, *e = text.unicode() + text.size(); @@ -212,6 +231,18 @@ QString textcmdStopColor() { return result.append(TextCommand).append(QChar(TextCommandNoColor)).append(TextCommand); } +QString textcmdStartSemibold() { + QString result; + result.reserve(3); + return result.append(TextCommand).append(QChar(TextCommandSemibold)).append(TextCommand); +} + +QString textcmdStopSemibold() { + QString result; + result.reserve(3); + return result.append(TextCommand).append(QChar(TextCommandNoSemibold)).append(TextCommand); +} + const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink) { const QChar *result = from + 1; if (*from != TextCommand || result >= end) return from; @@ -223,6 +254,8 @@ const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink) switch (cmd) { case TextCommandBold: case TextCommandNoBold: + case TextCommandSemibold: + case TextCommandNoSemibold: case TextCommandItalic: case TextCommandNoItalic: case TextCommandUnderline: @@ -263,7 +296,7 @@ const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink) class TextParser { public: - + static Qt::LayoutDirection stringDirection(const QString &str, int32 from, int32 to) { const ushort *p = reinterpret_cast(str.unicode()) + from; const ushort *end = p + (to - from); @@ -340,7 +373,7 @@ public: void getLinkData(const QString &original, QString &result, int32 &fullDisplayed) { if (!original.isEmpty() && original.at(0) == '/') { result = original; - fullDisplayed = -4; // bot command + fullDisplayed = -4; // bot command } else if (!original.isEmpty() && original.at(0) == '@') { result = original; fullDisplayed = -3; // mention @@ -454,7 +487,26 @@ public: while (waitingEntity != entitiesEnd && waitingEntity->length <= 0) ++waitingEntity; } } - + + bool readSkipBlockCommand() { + const QChar *afterCmd = textSkipCommand(ptr, end, links.size() < 0x7FFF); + if (afterCmd == ptr) { + return false; + } + + ushort cmd = (++ptr)->unicode(); + ++ptr; + + switch (cmd) { + case TextCommandSkipBlock: + createSkipBlock(ptr->unicode(), (ptr + 1)->unicode()); + break; + } + + ptr = afterCmd; + return true; + } + bool readCommand() { const QChar *afterCmd = textSkipCommand(ptr, end, links.size() < 0x7FFF); if (afterCmd == ptr) { @@ -479,6 +531,20 @@ public: } break; + case TextCommandSemibold: + if (!(flags & TextBlockFSemibold)) { + createBlock(); + flags |= TextBlockFSemibold; + } + break; + + case TextCommandNoSemibold: + if (flags & TextBlockFSemibold) { + createBlock(); + flags &= ~TextBlockFSemibold; + } + break; + case TextCommandItalic: if (!(flags & TextBlockFItalic)) { createBlock(); @@ -530,7 +596,6 @@ public: } break; case TextCommandSkipBlock: - createBlock(); createSkipBlock(ptr->unicode(), (ptr + 1)->unicode()); break; @@ -598,7 +663,7 @@ public: void parseEmojiFromCurrent() { int len = 0; - EmojiPtr e = emojiFromText(ptr - emojiLookback, end, len); + EmojiPtr e = emojiFromText(ptr - emojiLookback, end, &len); if (!e) return; for (int l = len - emojiLookback - 1; l > 0; --l) { @@ -703,6 +768,13 @@ public: if (sumFinished || _t->_text.size() >= 0x8000) break; // 32k max } createBlock(); + if (sumFinished && rich) { // we could've skipped the final skip block command + for (; ptr < end; ++ptr) { + if (*ptr == TextCommand && readSkipBlockCommand()) { + break; + } + } + } removeFlags.clear(); _t->_links.resize(maxLnkIndex); @@ -713,31 +785,29 @@ public: if (_t->_links.size() < lnkIndex) { _t->_links.resize(lnkIndex); const TextLinkData &data(links[lnkIndex - maxLnkIndex - 1]); - TextLinkPtr lnk; + ClickHandlerPtr lnk; if (data.fullDisplayed < -4) { // hidden link - lnk = TextLinkPtr(new CustomTextLink(data.url)); + lnk.reset(new HiddenUrlClickHandler(data.url)); } else if (data.fullDisplayed < -3) { // bot command - lnk = TextLinkPtr(new BotCommandLink(data.url)); + lnk.reset(new BotCommandClickHandler(data.url)); } else if (data.fullDisplayed < -2) { // mention if (options.flags & TextTwitterMentions) { - lnk = TextLinkPtr(new TextLink(qsl("https://twitter.com/") + data.url.mid(1), true)); + lnk.reset(new UrlClickHandler(qsl("https://twitter.com/") + data.url.mid(1), true)); } else if (options.flags & TextInstagramMentions) { - lnk = TextLinkPtr(new TextLink(qsl("https://instagram.com/") + data.url.mid(1) + '/', true)); + lnk.reset(new UrlClickHandler(qsl("https://instagram.com/") + data.url.mid(1) + '/', true)); } else { - lnk = TextLinkPtr(new MentionLink(data.url)); + lnk.reset(new MentionClickHandler(data.url)); } } else if (data.fullDisplayed < -1) { // hashtag if (options.flags & TextTwitterMentions) { - lnk = TextLinkPtr(new TextLink(qsl("https://twitter.com/hashtag/") + data.url.mid(1) + qsl("?src=hash"), true)); + lnk.reset(new UrlClickHandler(qsl("https://twitter.com/hashtag/") + data.url.mid(1) + qsl("?src=hash"), true)); } else if (options.flags & TextInstagramMentions) { - lnk = TextLinkPtr(new TextLink(qsl("https://instagram.com/explore/tags/") + data.url.mid(1) + '/', true)); + lnk.reset(new UrlClickHandler(qsl("https://instagram.com/explore/tags/") + data.url.mid(1) + '/', true)); } else { - lnk = TextLinkPtr(new HashtagLink(data.url)); + lnk.reset(new HashtagClickHandler(data.url)); } - } else if (data.fullDisplayed < 0) { // email - lnk = TextLinkPtr(new EmailLink(data.url)); - } else { - lnk = TextLinkPtr(new TextLink(data.url, data.fullDisplayed > 0)); + } else { // email or url + lnk.reset(new UrlClickHandler(data.url, data.fullDisplayed != 0)); } _t->setLink(lnkIndex, lnk); } @@ -884,64 +954,113 @@ namespace { } } -void TextLink::onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton || button == Qt::MiddleButton) { - QString url = TextLink::encoded(); - QRegularExpressionMatch telegramMeUser = QRegularExpression(qsl("^https?://telegram\\.me/([a-zA-Z0-9\\.\\_]+)/?(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url); - QRegularExpressionMatch telegramMeGroup = QRegularExpression(qsl("^https?://telegram\\.me/joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url); - QRegularExpressionMatch telegramMeStickers = QRegularExpression(qsl("^https?://telegram\\.me/addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url); - QRegularExpressionMatch telegramMeShareUrl = QRegularExpression(qsl("^https?://telegram\\.me/share/url\\?(.+)$"), QRegularExpression::CaseInsensitiveOption).match(url); - if (telegramMeUser.hasMatch()) { - QString params = url.mid(telegramMeUser.captured(0).size()); - url = qsl("tg://resolve/?domain=") + myUrlEncode(telegramMeUser.captured(1)) + (params.isEmpty() ? QString() : '&' + params); - } else if (telegramMeGroup.hasMatch()) { - url = qsl("tg://join?invite=") + myUrlEncode(telegramMeGroup.captured(1)); - } else if (telegramMeStickers.hasMatch()) { - url = qsl("tg://addstickers?set=") + myUrlEncode(telegramMeStickers.captured(1)); - } else if (telegramMeShareUrl.hasMatch()) { - url = qsl("tg://msg_url?") + telegramMeShareUrl.captured(1); - } +QString UrlClickHandler::copyToClipboardContextItem() const { + return lang(isEmail() ? lng_context_copy_email : lng_context_copy_link); +} - if (QRegularExpression(qsl("^tg://[a-zA-Z0-9]+"), QRegularExpression::CaseInsensitiveOption).match(url).hasMatch()) { - App::openLocalUrl(url); +namespace { + +QString tryConvertUrlToLocal(const QString &url) { + QRegularExpressionMatch telegramMeUser = QRegularExpression(qsl("^https?://telegram\\.me/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$|/(\\d+)/?(?:\\?|$))"), QRegularExpression::CaseInsensitiveOption).match(url); + QRegularExpressionMatch telegramMeGroup = QRegularExpression(qsl("^https?://telegram\\.me/joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url); + QRegularExpressionMatch telegramMeStickers = QRegularExpression(qsl("^https?://telegram\\.me/addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url); + QRegularExpressionMatch telegramMeShareUrl = QRegularExpression(qsl("^https?://telegram\\.me/share/url\\?(.+)$"), QRegularExpression::CaseInsensitiveOption).match(url); + if (telegramMeGroup.hasMatch()) { + return qsl("tg://join?invite=") + myUrlEncode(telegramMeGroup.captured(1)); + } else if (telegramMeStickers.hasMatch()) { + return qsl("tg://addstickers?set=") + myUrlEncode(telegramMeStickers.captured(1)); + } else if (telegramMeShareUrl.hasMatch()) { + return qsl("tg://msg_url?") + telegramMeShareUrl.captured(1); + } else if (telegramMeUser.hasMatch()) { + QString params = url.mid(telegramMeUser.captured(0).size()), postParam; + if (QRegularExpression(qsl("^/\\d+/?(?:\\?|$)")).match(telegramMeUser.captured(2)).hasMatch()) { + postParam = qsl("&post=") + telegramMeUser.captured(3); + } + return qsl("tg://resolve/?domain=") + myUrlEncode(telegramMeUser.captured(1)) + postParam + (params.isEmpty() ? QString() : '&' + params); + } + return url; +} + +} // namespace + +void UrlClickHandler::doOpen(QString url) { + PopupTooltip::Hide(); + + if (isEmail(url)) { + QUrl u(qstr("mailto:") + url); + if (!QDesktopServices::openUrl(u)) { + psOpenFile(u.toString(QUrl::FullyEncoded), true); + } + return; + } + + url = tryConvertUrlToLocal(url); + + if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) { + App::openLocalUrl(url); + } else { + QDesktopServices::openUrl(url); + } +} + +void HiddenUrlClickHandler::onClick(Qt::MouseButton button) const { + QString u = url(); + + u = tryConvertUrlToLocal(u); + + if (u.startsWith(qstr("tg://"))) { + App::openLocalUrl(u); + } else { + Ui::showLayer(new ConfirmLinkBox(u)); + } +} + +QString LocationClickHandler::copyToClipboardContextItem() const { + return lang(lng_context_copy_link); +} + +void LocationClickHandler::onClick(Qt::MouseButton button) const { + if (!psLaunchMaps(_coords)) { + QDesktopServices::openUrl(_text); + } +} + +void LocationClickHandler::setup() { + QString latlon(qsl("%1,%2").arg(_coords.lat).arg(_coords.lon)); + _text = qsl("https://maps.google.com/maps?q=") + latlon + qsl("&ll=") + latlon + qsl("&z=16"); +} + +QString MentionClickHandler::copyToClipboardContextItem() const { + return lang(lng_context_copy_mention); +} + +void MentionClickHandler::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton || button == Qt::MiddleButton) { + App::openPeerByName(_tag.mid(1), ShowAtProfileMsgId); + } +} + +QString HashtagClickHandler::copyToClipboardContextItem() const { + return lang(lng_context_copy_hashtag); +} + +void HashtagClickHandler::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton || button == Qt::MiddleButton) { + App::searchByHashtag(_tag, Ui::getPeerForMouseAction()); + } +} + +void BotCommandClickHandler::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton || button == Qt::MiddleButton) { + if (PeerData *peer = Ui::getPeerForMouseAction()) { + Ui::showPeerHistory(peer, ShowAtTheEndMsgId); + App::sendBotCommand(peer, _cmd); } else { - QDesktopServices::openUrl(url); + App::insertBotCommand(_cmd); } } } -void EmailLink::onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton || button == Qt::MiddleButton) { - QUrl url(qstr("mailto:") + _email); - if (!QDesktopServices::openUrl(url)) { - psOpenFile(url.toString(QUrl::FullyEncoded), true); - } - } -} - -void CustomTextLink::onClick(Qt::MouseButton button) const { - Ui::showLayer(new ConfirmLinkBox(text())); -} - -void MentionLink::onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton || button == Qt::MiddleButton) { - App::openPeerByName(_tag.mid(1), true); - } -} - -void HashtagLink::onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton || button == Qt::MiddleButton) { - App::searchByHashtag(_tag, App::mousedItem() ? App::mousedItem()->history()->peer : 0); - } -} - -void BotCommandLink::onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton || button == Qt::MiddleButton) { -// App::insertBotCommand(_cmd); - App::sendBotCommand(_cmd); - } -} - class TextPainter { public: @@ -952,7 +1071,7 @@ public: return _blockEnd(t, i, e) - (*i)->from(); } - TextPainter(QPainter *p, const Text *t) : _p(p), _t(t), _elideLast(false), _elideRemoveFromEnd(0), _str(0), _elideSavedBlock(0), _lnkResult(0), _inTextFlag(0), _getSymbol(0), _getSymbolAfter(0), _getSymbolUpon(0) { + TextPainter(QPainter *p, const Text *t) : _p(p), _t(t), _elideLast(false), _breakEverywhere(false), _elideRemoveFromEnd(0), _str(0), _elideSavedBlock(0), _lnkResult(0), _inTextFlag(0), _getSymbol(0), _getSymbolAfter(0), _getSymbolUpon(0) { } void initNextParagraph(Text::TextBlocks::const_iterator i) { @@ -975,7 +1094,7 @@ public: void initParagraphBidi() { if (!_parLength || !_parAnalysis.isEmpty()) return; - + Text::TextBlocks::const_iterator i = _parStartBlock, e = _t->_blocks.cend(), n = i + 1; bool ignore = false; @@ -1111,6 +1230,15 @@ public: if (_btype == TextBlockTText) { TextBlock *t = static_cast(b); + if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line + last_rPadding += lpadding; + + _lineHeight = qMax(_lineHeight, blockHeight); + + longWordLine = false; + continue; + } + QFixed f_wLeft = _wLeft; // vars for saving state of the last word start int32 f_lineHeight = _lineHeight; // f points to the last word-start element of t->_words for (TextBlock::TextWords::const_iterator j = t->_words.cbegin(), en = t->_words.cend(), f = j; j != en; ++j) { @@ -1141,7 +1269,7 @@ public: bool elidedLine = _elideLast && (_y + elidedLineHeight >= _yToElide); if (elidedLine) { _lineHeight = elidedLineHeight; - } else if (f != j) { + } else if (f != j && !_breakEverywhere) { // word did not fit completely, so we roll back the state to the beginning of this long word j = f; _wLeft = f_wLeft; @@ -1166,28 +1294,6 @@ public: f_wLeft = _wLeft; f_lineHeight = _lineHeight; } - if (lpadding > 0) { // no words in this block, spaces only - int32 elidedLineHeight = qMax(_lineHeight, blockHeight); - bool elidedLine = _elideLast && (_y + elidedLineHeight >= _yToElide); - if (elidedLine) { - _lineHeight = elidedLineHeight; - } - ushort nextStart = _blockEnd(_t, i, e); - if (!drawLine(nextStart, i + 1, e)) return; - _y += _lineHeight; - _lineHeight = qMax(0, blockHeight); - _lineStart = nextStart; - _lineStartBlock = blockIndex + 1; - - last_rBearing = _rb; - last_rPadding = b->rpadding(); - _wLeft = _w; - if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) { - _wLeft -= _elideRemoveFromEnd; - } - - longWordLine = true; - } continue; } @@ -1222,7 +1328,7 @@ public: } } - void drawElided(int32 left, int32 top, int32 w, style::align align, int32 lines, int32 yFrom, int32 yTo, int32 removeFromEnd) { + void drawElided(int32 left, int32 top, int32 w, style::align align, int32 lines, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere) { if (lines <= 0 || _t->isNull()) return; if (yTo < 0 || (lines - 1) * _t->_font->height < yTo) { @@ -1230,21 +1336,24 @@ public: _elideLast = true; _elideRemoveFromEnd = removeFromEnd; } + _breakEverywhere = breakEverywhere; draw(left, top, w, align, yFrom, yTo); } - const TextLinkPtr &link(int32 x, int32 y, int32 w, style::align align) { + const ClickHandlerPtr &link(int32 x, int32 y, int32 w, style::align align) { + StaticNeverFreedPointer zero(new ClickHandlerPtr()); + _lnkX = x; _lnkY = y; - _lnkResult = &_zeroLnk; + _lnkResult = zero.data(); if (!_t->isNull() && _lnkX >= 0 && _lnkX < w && _lnkY >= 0) { draw(0, 0, w, align, _lnkY, _lnkY + 1); } return *_lnkResult; } - void getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y, int32 w, style::align align) { - lnk = TextLinkPtr(); + void getState(ClickHandlerPtr &lnk, bool &inText, int32 x, int32 y, int32 w, style::align align, bool breakEverywhere) { + lnk.clear(); inText = false; if (!_t->isNull() && x >= 0 && x < w && y >= 0) { @@ -1252,6 +1361,7 @@ public: _lnkY = y; _lnkResult = &lnk; _inTextFlag = &inText; + _breakEverywhere = breakEverywhere; draw(0, 0, w, align, _lnkY, _lnkY + 1); lnk = *_lnkResult; } @@ -1276,11 +1386,8 @@ public: return block->color()->p; } if (block->lnkIndex()) { - const TextLinkPtr &l(_t->_links.at(block->lnkIndex() - 1)); - if (l == _overLnk) { - if (l == _downLnk) { - return _textStyle->linkFgDown->p; - } + if (ClickHandler::showAsPressed(_t->_links.at(block->lnkIndex() - 1))) { + return _textStyle->linkFgDown->p; } return _textStyle->linkFg->p; } @@ -1340,7 +1447,7 @@ public: *_getSymbolAfter = false; *_getSymbolUpon = ((_lnkX >= _x) && (_lineStart > 0)) ? true : false; } - return false; + return false; } else if (_lnkX >= x + (_w - _wLeft)) { if (_parDirection == Qt::RightToLeft) { *_getSymbol = _lineStart; @@ -1595,6 +1702,7 @@ public: *_getSymbolUpon = true; return false; } else if (_p) { +#ifndef TDESKTOP_WINRT // temp QTextCharFormat format; QTextItemInt gf(glyphs.mid(glyphsStart, glyphsEnd - glyphsStart), &_e->fnt, engine.layoutData->string.unicode() + itemStart, @@ -1603,7 +1711,7 @@ public: gf.width = itemWidth; gf.justified = false; gf.initWithScriptItem(si); - +#endif // !TDESKTOP_WINRT if (_localFrom + itemStart < _selectedTo && _localFrom + itemEnd > _selectedFrom) { QFixed selX = x, selWidth = itemWidth; if (_localFrom + itemEnd > _selectedTo || _localFrom + itemStart < _selectedFrom) { @@ -1644,7 +1752,9 @@ public: _p->fillRect(QRectF(selX.toReal(), _y + _yDelta, selWidth.toReal(), _fontHeight), _textStyle->selectBg->b); } +#ifndef TDESKTOP_WINRT // temp _p->drawTextItem(QPointF(x.toReal(), textY), gf); +#endif // !TDESKTOP_WINRT } x += itemWidth; @@ -1838,15 +1948,14 @@ public: newFont = applyFlags(flags, _t->_font); } if (block->lnkIndex()) { - const TextLinkPtr &l(_t->_links.at(block->lnkIndex() - 1)); - if (l == _overLnk) { - if (l == _downLnk || !_downLnk) { - if (_t->_font != _textStyle->linkFlagsOver) newFont = _textStyle->linkFlagsOver; - } else { - if (_t->_font != _textStyle->linkFlags) newFont = _textStyle->linkFlags; + if (ClickHandler::showAsActive(_t->_links.at(block->lnkIndex() - 1))) { + if (_t->_font != _textStyle->linkFlagsOver) { + newFont = _textStyle->linkFlagsOver; } } else { - if (_t->_font != _textStyle->linkFlags) newFont = _textStyle->linkFlags; + if (_t->_font != _textStyle->linkFlags) { + newFont = _textStyle->linkFlags; + } } } if (newFont != _f) { @@ -2408,7 +2517,7 @@ private: QPainter *_p; const Text *_t; - bool _elideLast; + bool _elideLast, _breakEverywhere; int32 _elideRemoveFromEnd; style::align _align; QPen _originalPen; @@ -2428,7 +2537,7 @@ private: style::font _f; QFixed _x, _w, _wLeft; int32 _y, _yDelta, _lineHeight, _fontHeight; - + // elided hack support int32 _blocksSize; int32 _elideSavedIndex; @@ -2440,7 +2549,7 @@ private: // link and symbol resolve QFixed _lnkX; int32 _lnkY; - const TextLinkPtr *_lnkResult; + const ClickHandlerPtr *_lnkResult; bool *_inTextFlag; uint16 *_getSymbol; bool *_getSymbolAfter, *_getSymbolUpon; @@ -2472,20 +2581,32 @@ Text::Text(style::font font, const QString &text, const TextParseOptions &option } } -Text::Text(const Text &other) : -_minResizeWidth(other._minResizeWidth), _maxWidth(other._maxWidth), -_minHeight(other._minHeight), -_text(other._text), -_font(other._font), -_blocks(other._blocks.size()), -_links(other._links), -_startDir(other._startDir) -{ +Text::Text(const Text &other) +: _minResizeWidth(other._minResizeWidth) +, _maxWidth(other._maxWidth) +, _minHeight(other._minHeight) +, _text(other._text) +, _font(other._font) +, _blocks(other._blocks.size()) +, _links(other._links) +, _startDir(other._startDir) { for (int32 i = 0, l = _blocks.size(); i < l; ++i) { _blocks[i] = other._blocks.at(i)->clone(); } } +Text::Text(Text &&other) +: _minResizeWidth(other._minResizeWidth) +, _maxWidth(other._maxWidth) +, _minHeight(other._minHeight) +, _text(other._text) +, _font(other._font) +, _blocks(other._blocks) +, _links(other._links) +, _startDir(other._startDir) { + other.clearFields(); +} + Text &Text::operator=(const Text &other) { _minResizeWidth = other._minResizeWidth; _maxWidth = other._maxWidth; @@ -2501,10 +2622,23 @@ Text &Text::operator=(const Text &other) { return *this; } +Text &Text::operator=(Text &&other) { + _minResizeWidth = other._minResizeWidth; + _maxWidth = other._maxWidth; + _minHeight = other._minHeight; + _text = other._text; + _font = other._font; + _blocks = other._blocks; + _links = other._links; + _startDir = other._startDir; + other.clearFields(); + return *this; +} + void Text::setText(style::font font, const QString &text, const TextParseOptions &options) { if (!_textStyle) _initDefault(); _font = font; - clean(); + clear(); { TextParser parser(this, text, options); } @@ -2582,17 +2716,8 @@ void Text::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) { void Text::setMarkedText(style::font font, const QString &text, const EntitiesInText &entities, const TextParseOptions &options) { if (!_textStyle) _initDefault(); _font = font; - clean(); + clear(); { -// QByteArray ba = text.toUtf8(); // chars for OS X crash investigation -// const char *ch = ba.constData(); -// LOG(("STR: %1").arg(text)); -// LOG(("BYTES: %1").arg(mb(ba.constData(), ba.size()).str())); -// for (int32 i = 0; i < text.size(); ++i) { -// LOG(("LETTER %1: '%2' - %3").arg(i).arg(text.at(i)).arg(text.at(i).unicode())); -// } -// int32 w = _font->width(text); - // QString newText; // utf16 of the text for emoji // newText.reserve(8 * text.size()); // for (const QChar *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) { @@ -2706,7 +2831,7 @@ void Text::setRichText(style::font font, const QString &text, TextParseOptions o setText(font, parsed, options); } -void Text::setLink(uint16 lnkIndex, const TextLinkPtr &lnk) { +void Text::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { if (!lnkIndex || lnkIndex > _links.size()) return; _links[lnkIndex - 1] = lnk; } @@ -2735,6 +2860,111 @@ void Text::removeSkipBlock() { } } +int32 Text::countWidth(int32 w) const { + QFixed width = w; + if (width < _minResizeWidth) width = _minResizeWidth; + if (width >= _maxWidth) { + return _maxWidth.ceil().toInt(); + } + + QFixed minWidthLeft = width, widthLeft = width, last_rBearing = 0, last_rPadding = 0; + bool longWordLine = true; + for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); i != e; ++i) { + ITextBlock *b = *i; + TextBlockType _btype = b->type(); + int32 blockHeight = _blockHeight(b, _font); + QFixed _rb = _blockRBearing(b); + + if (_btype == TextBlockTNewline) { + last_rBearing = _rb; + last_rPadding = b->f_rpadding(); + if (widthLeft < minWidthLeft) { + minWidthLeft = widthLeft; + } + widthLeft = width - (b->f_width() - last_rBearing); + + longWordLine = true; + continue; + } + QFixed lpadding = b->f_lpadding(); + QFixed newWidthLeft = widthLeft - lpadding - last_rBearing - (last_rPadding + b->f_width() - _rb); + if (newWidthLeft >= 0) { + last_rBearing = _rb; + last_rPadding = b->f_rpadding(); + widthLeft = newWidthLeft; + + longWordLine = false; + continue; + } + + if (_btype == TextBlockTText) { + TextBlock *t = static_cast(b); + if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line + last_rPadding += lpadding; + + longWordLine = false; + continue; + } + + QFixed f_wLeft = widthLeft; + for (TextBlock::TextWords::const_iterator j = t->_words.cbegin(), e = t->_words.cend(), f = j; j != e; ++j) { + bool wordEndsHere = (j->width >= 0); + QFixed j_width = wordEndsHere ? j->width : -j->width; + + QFixed newWidthLeft = widthLeft - lpadding - last_rBearing - (last_rPadding + j_width - j->f_rbearing()); + lpadding = 0; + if (newWidthLeft >= 0) { + last_rBearing = j->f_rbearing(); + last_rPadding = j->rpadding; + widthLeft = newWidthLeft; + + if (wordEndsHere) { + longWordLine = false; + } + if (wordEndsHere || longWordLine) { + f_wLeft = widthLeft; + f = j + 1; + } + continue; + } + + if (f != j) { + j = f; + widthLeft = f_wLeft; + j_width = (j->width >= 0) ? j->width : -j->width; + } + + last_rBearing = j->f_rbearing(); + last_rPadding = j->rpadding; + if (widthLeft < minWidthLeft) { + minWidthLeft = widthLeft; + } + widthLeft = width - (j_width - last_rBearing); + + longWordLine = true; + f = j + 1; + f_wLeft = widthLeft; + } + continue; + } + + last_rBearing = _rb; + last_rPadding = b->f_rpadding(); + if (widthLeft < minWidthLeft) { + minWidthLeft = widthLeft; + } + widthLeft = width - (b->f_width() - last_rBearing); + + longWordLine = true; + continue; + } + if (widthLeft < minWidthLeft) { + minWidthLeft = widthLeft; + } + + return (width - minWidthLeft).ceil().toInt(); +} + int32 Text::countHeight(int32 w) const { QFixed width = w; if (width < _minResizeWidth) width = _minResizeWidth; @@ -2762,8 +2992,8 @@ int32 Text::countHeight(int32 w) const { longWordLine = true; continue; } - widthLeft -= b->f_lpadding(); - QFixed newWidthLeft = widthLeft - last_rBearing - (last_rPadding + b->f_width() - _rb); + QFixed lpadding = b->f_lpadding(); + QFixed newWidthLeft = widthLeft - lpadding - last_rBearing - (last_rPadding + b->f_width() - _rb); if (newWidthLeft >= 0) { last_rBearing = _rb; last_rPadding = b->f_rpadding(); @@ -2777,13 +3007,23 @@ int32 Text::countHeight(int32 w) const { if (_btype == TextBlockTText) { TextBlock *t = static_cast(b); + if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line + last_rPadding += lpadding; + + lineHeight = qMax(lineHeight, blockHeight); + + longWordLine = false; + continue; + } + QFixed f_wLeft = widthLeft; int32 f_lineHeight = lineHeight; for (TextBlock::TextWords::const_iterator j = t->_words.cbegin(), e = t->_words.cend(), f = j; j != e; ++j) { bool wordEndsHere = (j->width >= 0); QFixed j_width = wordEndsHere ? j->width : -j->width; - QFixed newWidthLeft = widthLeft - last_rBearing - (last_rPadding + j_width - j->f_rbearing()); + QFixed newWidthLeft = widthLeft - lpadding - last_rBearing - (last_rPadding + j_width - j->f_rbearing()); + lpadding = 0; if (newWidthLeft >= 0) { last_rBearing = j->f_rbearing(); last_rPadding = j->rpadding; @@ -2849,20 +3089,20 @@ void Text::draw(QPainter &painter, int32 left, int32 top, int32 w, style::align p.draw(left, top, w, align, yFrom, yTo, selectedFrom, selectedTo); } -void Text::drawElided(QPainter &painter, int32 left, int32 top, int32 w, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd) const { +void Text::drawElided(QPainter &painter, int32 left, int32 top, int32 w, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere) const { // painter.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug TextPainter p(&painter, this); - p.drawElided(left, top, w, align, lines, yFrom, yTo, removeFromEnd); + p.drawElided(left, top, w, align, lines, yFrom, yTo, removeFromEnd, breakEverywhere); } -const TextLinkPtr &Text::link(int32 x, int32 y, int32 width, style::align align) const { +const ClickHandlerPtr &Text::link(int32 x, int32 y, int32 width, style::align align) const { TextPainter p(0, this); return p.link(x, y, width, align); } -void Text::getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y, int32 width, style::align align) const { +void Text::getState(ClickHandlerPtr &lnk, bool &inText, int32 x, int32 y, int32 width, style::align align, bool breakEverywhere) const { TextPainter p(0, this); - p.getState(lnk, inText, x, y, width, align); + p.getState(lnk, inText, x, y, width, align, breakEverywhere); } void Text::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y, int32 width, style::align align) const { @@ -2909,23 +3149,23 @@ uint32 Text::adjustSelection(uint16 from, uint16 to, TextSelectType selectType) } QString Text::original(uint16 selectedFrom, uint16 selectedTo, ExpandLinksMode mode) const { - QString result; + QString result, emptyurl; result.reserve(_text.size()); int32 lnkFrom = 0, lnkIndex = 0; - for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) { + for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) { int32 blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex(); int32 blockFrom = (i == e) ? _text.size() : (*i)->from(); if (blockLnkIndex != lnkIndex) { if (lnkIndex) { // write link - const TextLinkPtr &lnk(_links.at(lnkIndex - 1)); - const QString &url(lnk ? lnk->text() : QString()); + const ClickHandlerPtr &lnk(_links.at(lnkIndex - 1)); + const QString &url = (mode == ExpandLinksNone || !lnk) ? emptyurl : lnk->text(); int32 rangeFrom = qMax(int32(selectedFrom), lnkFrom), rangeTo = qMin(blockFrom, int32(selectedTo)); if (rangeTo > rangeFrom) { QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom); - if (url.isEmpty() || mode == ExpandLinksNone || lnkFrom != rangeFrom || blockFrom != rangeTo) { + if (url.isEmpty() || lnkFrom != rangeFrom || blockFrom != rangeTo) { result += r; } else { QUrl u(url); @@ -2962,6 +3202,7 @@ QString Text::original(uint16 selectedFrom, uint16 selectedTo, ExpandLinksMode m EntitiesInText Text::originalEntities() const { EntitiesInText result; + QString emptyurl; int32 originalLength = 0, lnkStart = 0, italicStart = 0, boldStart = 0, codeStart = 0, preStart = 0; int32 lnkFrom = 0, lnkIndex = 0, flags = 0; @@ -2994,8 +3235,8 @@ EntitiesInText Text::originalEntities() const { } if (blockLnkIndex != lnkIndex) { if (lnkIndex) { // write link - const TextLinkPtr &lnk(_links.at(lnkIndex - 1)); - const QString &url(lnk ? lnk->text() : QString()); + const ClickHandlerPtr &lnk(_links.at(lnkIndex - 1)); + const QString &url(lnk ? lnk->text() : emptyurl); int32 rangeFrom = lnkFrom, rangeTo = blockFrom; if (rangeTo > rangeFrom) { @@ -3048,10 +3289,14 @@ EntitiesInText Text::originalEntities() const { return result; } -void Text::clean() { +void Text::clear() { for (TextBlocks::iterator i = _blocks.begin(), e = _blocks.end(); i != e; ++i) { delete *i; } + clearFields(); +} + +void Text::clearFields() { _blocks.clear(); _links.clear(); _maxWidth = _minHeight = 0; @@ -3156,7 +3401,8 @@ namespace { class BlockParser { public: - BlockParser(QTextEngine *e, TextBlock *b, QFixed minResizeWidth, int32 blockFrom) : block(b), eng(e) { + BlockParser(QTextEngine *e, TextBlock *b, QFixed minResizeWidth, int32 blockFrom, const QString &str) + : block(b), eng(e), str(str) { parseWords(minResizeWidth, blockFrom); } @@ -3234,7 +3480,7 @@ public: if (lbh.currentPosition >= eng->layoutData->string.length() || attributes[lbh.currentPosition].whiteSpace - || attributes[lbh.currentPosition].lineBreak) { + || isLineBreak(attributes, lbh.currentPosition)) { lbh.adjustRightBearing(); block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, qMin(QFixed(), lbh.rightBearing))); block->_width += lbh.tmpData.textWidth; @@ -3281,10 +3527,19 @@ public: } } + bool isLineBreak(const QCharAttributes *attributes, int32 index) { + bool lineBreak = attributes[index].lineBreak; + if (lineBreak && block->lnkIndex() > 0 && index > 0 && str.at(index - 1) == '/') { + return false; // don't break after / in links + } + return lineBreak; + } + private: TextBlock *block; QTextEngine *eng; + const QString &str; }; @@ -3293,7 +3548,7 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi if (length) { style::font blockFont = font; if (!flags && lnkIndex) { - // should use textStyle lnkFlags somehow.. not supported + // should use textStyle lnkFlags somehow... not supported } if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) { @@ -3318,14 +3573,22 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi } } - QStackTextEngine engine(str.mid(_from, length), blockFont->f); + QString part = str.mid(_from, length); + QStackTextEngine engine(part, blockFont->f); engine.itemize(); QTextLayout layout(&engine); layout.beginLayout(); layout.createLine(); - BlockParser parser(&engine, this, minResizeWidth, _from); + bool logCrashString = (rand_value() % 4 == 1); + if (logCrashString) { + SignalHandlers::setCrashAnnotationRef("CrashString", &str); + } + BlockParser parser(&engine, this, minResizeWidth, _from, part); + if (logCrashString) { + SignalHandlers::clearCrashAnnotationRef("CrashString"); + } layout.endLayout(); } @@ -3687,7 +3950,7 @@ void initLinkSets() { namespace { // accent char list taken from https://github.com/aristus/accent-folding - inline QChar chNoAccent(int32 code) { + inline QChar chNoAccent(int32 code) { switch (code) { case 7834: return QChar(97); case 193: return QChar(97); @@ -4383,6 +4646,7 @@ namespace { case 65359: return QChar(111); case 65363: return QChar(115); case 65367: return QChar(119); + case 1105: return QChar(1077); default: break; } @@ -4411,7 +4675,7 @@ QString textAccentFold(const QString &text) { result[i] = noAccent; } else { if (copying) result[i] = *ch; - ++ch, ++i; + ++ch, ++i; if (copying) result[i] = *ch; } } else { @@ -4494,8 +4758,7 @@ goodCanBreakEntity = canBreakEntity;\ #undef MARK_GOOD_AS_LEVEL int elen = 0; - EmojiPtr e = emojiFromText(ch, end, elen); - if (e) { + if (EmojiPtr e = emojiFromText(ch, end, &elen)) { for (int i = 0; i < elen; ++i, ++ch, ++s) { if (ch->isHighSurrogate() && i + 1 < elen && (ch + 1)->isLowSurrogate()) { ++ch; @@ -4902,6 +5165,58 @@ EntitiesInText textParseEntities(QString &text, int32 flags, bool rich) { // som return result; } +QString textApplyEntities(const QString &text, const EntitiesInText &entities) { + if (entities.isEmpty()) return text; + + QMultiMap closingTags; + QString code(qsl("`")), pre(qsl("```")); + + QString result; + int32 size = text.size(); + const QChar *b = text.constData(), *already = b, *e = b + size; + EntitiesInText::const_iterator entity = entities.cbegin(), end = entities.cend(); + while (entity != end && ((entity->type != EntityInTextCode && entity->type != EntityInTextPre) || entity->length <= 0 || entity->offset >= size)) { + ++entity; + } + while (entity != end || !closingTags.isEmpty()) { + int32 nextOpenEntity = (entity == end) ? (size + 1) : entity->offset; + int32 nextCloseEntity = closingTags.isEmpty() ? (size + 1) : closingTags.cbegin().key(); + if (nextOpenEntity <= nextCloseEntity) { + QString tag = (entity->type == EntityInTextCode) ? code : pre; + if (result.isEmpty()) result.reserve(text.size() + entities.size() * pre.size() * 2); + + const QChar *offset = b + nextOpenEntity; + if (offset > already) { + result.append(already, offset - already); + already = offset; + } + result.append(tag); + closingTags.insert(qMin(entity->offset + entity->length, size), tag); + + ++entity; + while (entity != end && ((entity->type != EntityInTextCode && entity->type != EntityInTextPre) || entity->length <= 0 || entity->offset >= size)) { + ++entity; + } + } else { + const QChar *offset = b + nextCloseEntity; + if (offset > already) { + result.append(already, offset - already); + already = offset; + } + result.append(closingTags.cbegin().value()); + closingTags.erase(closingTags.begin()); + } + } + if (result.isEmpty()) { + return text; + } + const QChar *offset = b + size; + if (offset > already) { + result.append(already, offset - already); + } + return result; +} + void emojiDraw(QPainter &p, EmojiPtr e, int x, int y) { p.drawPixmap(QPoint(x, y), App::emoji(), QRect(e->x * ESize, e->y * ESize, ESize, ESize)); } diff --git a/Telegram/SourceFiles/gui/text.h b/Telegram/SourceFiles/gui/text.h index bd95be3163..997f3e1a78 100644 --- a/Telegram/SourceFiles/gui/text.h +++ b/Telegram/SourceFiles/gui/text.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -110,6 +110,7 @@ inline MTPVector linksToMTP(const EntitiesInText &links, bool return result; } EntitiesInText textParseEntities(QString &text, int32 flags, bool rich = false); // changes text if (flags & TextParseMono) +QString textApplyEntities(const QString &text, const EntitiesInText &entities); #include "gui/emoji_config.h" @@ -308,201 +309,340 @@ private: friend class TextPainter; }; -class ITextLink { -public: +class ClickHandler; +using ClickHandlerPtr = QSharedPointer; - virtual void onClick(Qt::MouseButton) const = 0; - virtual const QString &text() const { - static const QString _tmp; - return _tmp; +class ClickHandlerHost { +protected: + + virtual void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { } - virtual const QString &readable() const { - static const QString _tmp; - return _tmp; - } - virtual bool fullDisplayed() const { - return true; - } - virtual void setFullDisplayed(bool full) { - } - virtual QString encoded() const { - return QString(); - } - virtual const QLatin1String &type() const = 0; - virtual ~ITextLink() { + virtual void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) { } + virtual ~ClickHandlerHost() = 0; + friend class ClickHandler; }; -#define TEXT_LINK_CLASS(ClassName) public: \ -const QLatin1String &type() const { \ - static const QLatin1String _type(qstr(#ClassName)); \ - return _type; \ -} - -typedef QSharedPointer TextLinkPtr; - -class TextLink : public ITextLink { - TEXT_LINK_CLASS(TextLink) - +class ClickHandler { public: - TextLink(const QString &url, bool fullDisplayed = true) : _url(url), _fullDisplayed(fullDisplayed) { - QUrl u(_url), good(u.isValid() ? u.toEncoded() : QString()); - _readable = good.isValid() ? good.toDisplayString() : _url; + virtual void onClick(Qt::MouseButton) const = 0; + + virtual QString tooltip() const { + return QString(); + } + virtual void copyToClipboard() const { + } + virtual QString copyToClipboardContextItem() const { + return QString(); + } + virtual QString text() const { + return QString(); + } + virtual QString dragText() const { + return text(); } - const QString &text() const { - return _url; + virtual ~ClickHandler() { } - void onClick(Qt::MouseButton button) const; + // this method should be called on mouse over a click handler + // it returns true if something was changed or false otherwise + static bool setActive(const ClickHandlerPtr &p, ClickHandlerHost *host = nullptr); - const QString &readable() const { - return _readable; + // this method should be called when mouse leaves the host + // it returns true if something was changed or false otherwise + static bool clearActive(ClickHandlerHost *host = nullptr) { + if (host && _activeHost != host) { + return false; + } + return setActive(ClickHandlerPtr(), host); } - bool fullDisplayed() const { - return _fullDisplayed; + // this method should be called on mouse pressed + static void pressed() { + unpressed(); + if (!_active || !*_active) { + return; + } + _pressed.makeIfNull(); + *_pressed = *_active; + if ((_pressedHost = _activeHost)) { + _pressedHost->clickHandlerPressedChanged(*_pressed, true); + } + } + + // this method should be called on mouse released + // the activated click handler is returned + static ClickHandlerPtr unpressed() { + if (_pressed && *_pressed) { + bool activated = (_active && *_active == *_pressed); + ClickHandlerPtr waspressed = *_pressed; + (*_pressed).clear(); + if (_pressedHost) { + _pressedHost->clickHandlerPressedChanged(waspressed, false); + _pressedHost = nullptr; + } + + if (activated) { + return *_active; + } else if (_active && *_active && _activeHost) { + // emit clickHandlerActiveChanged for current active + // click handler, which we didn't emit while we has + // a pressed click handler + _activeHost->clickHandlerActiveChanged(*_active, true); + } + } + return ClickHandlerPtr(); + } + + static ClickHandlerPtr getActive() { + return _active ? *_active : ClickHandlerPtr(); + } + static ClickHandlerPtr getPressed() { + return _pressed ? *_pressed : ClickHandlerPtr(); + } + + static bool showAsActive(const ClickHandlerPtr &p) { + if (!p || !_active || p != *_active) { + return false; + } + return !_pressed || !*_pressed || (p == *_pressed); + } + static bool showAsPressed(const ClickHandlerPtr &p) { + if (!p || !_active || p != *_active) { + return false; + } + return _pressed && (p == *_pressed); + } + static void hostDestroyed(ClickHandlerHost *host) { + if (_activeHost == host) { + _activeHost = nullptr; + } else if (_pressedHost == host) { + _pressedHost = nullptr; + } + } + +private: + + static NeverFreedPointer _active; + static NeverFreedPointer _pressed; + static ClickHandlerHost *_activeHost; + static ClickHandlerHost *_pressedHost; + +}; + +class LeftButtonClickHandler : public ClickHandler { +public: + void onClick(Qt::MouseButton button) const override final { + if (button != Qt::LeftButton) return; + onClickImpl(); + } + +protected: + virtual void onClickImpl() const = 0; + +}; + +class TextClickHandler : public ClickHandler { +public: + + TextClickHandler(bool fullDisplayed = true) : _fullDisplayed(fullDisplayed) { + } + + void copyToClipboard() const override { + QString u = url(); + if (!u.isEmpty()) { + QApplication::clipboard()->setText(u); + } + } + + QString tooltip() const override { + return _fullDisplayed ? QString() : readable(); } void setFullDisplayed(bool full) { _fullDisplayed = full; } - QString encoded() const { - QUrl u(_url), good(u.isValid() ? u.toEncoded() : QString()); - QString result(good.isValid() ? QString::fromUtf8(good.toEncoded()) : _url); - - if (!QRegularExpression(qsl("^[a-zA-Z]+://")).match(result).hasMatch()) { // no protocol - return qsl("http://") + result; - } - return result; +protected: + virtual QString url() const = 0; + virtual QString readable() const { + return url(); } -private: - - QString _url, _readable; bool _fullDisplayed; }; -class CustomTextLink : public TextLink { +class UrlClickHandler : public TextClickHandler { public: - - CustomTextLink(const QString &url) : TextLink(url, false) { + UrlClickHandler(const QString &url, bool fullDisplayed = true) : TextClickHandler(fullDisplayed), _url(url) { + if (isEmail()) { + _readable = _url; + } else { + QUrl u(_url), good(u.isValid() ? u.toEncoded() : QString()); + _readable = good.isValid() ? good.toDisplayString() : _url; + } } - void onClick(Qt::MouseButton button) const; + QString copyToClipboardContextItem() const override; + + QString text() const override { + return _url; + } + QString dragText() const override { + return url(); + } + + static void doOpen(QString url); + void onClick(Qt::MouseButton button) const override { + if (button == Qt::LeftButton || button == Qt::MiddleButton) { + doOpen(url()); + } + } + +protected: + QString url() const override { + if (isEmail()) { + return _url; + } + + QUrl u(_url), good(u.isValid() ? u.toEncoded() : QString()); + QString result(good.isValid() ? QString::fromUtf8(good.toEncoded()) : _url); + + if (!QRegularExpression(qsl("^[a-zA-Z]+:")).match(result).hasMatch()) { // no protocol + return qsl("http://") + result; + } + return result; + } + QString readable() const override { + return _readable; + } + +private: + static bool isEmail(const QString &url) { + int at = url.indexOf('@'), slash = url.indexOf('/'); + return ((at > 0) && (slash < 0 || slash > at)); + } + bool isEmail() const { + return isEmail(_url); + } + + QString _url, _readable; + +}; +typedef QSharedPointer TextClickHandlerPtr; + +class HiddenUrlClickHandler : public UrlClickHandler { +public: + HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) { + } + void onClick(Qt::MouseButton button) const override; + }; -class EmailLink : public ITextLink { - TEXT_LINK_CLASS(EmailLink) +struct LocationCoords { + LocationCoords() : lat(0), lon(0) { + } + LocationCoords(float64 lat, float64 lon) : lat(lat), lon(lon) { + } + LocationCoords(const MTPDgeoPoint &point) : lat(point.vlat.v), lon(point.vlong.v) { + } + float64 lat, lon; +}; +inline bool operator==(const LocationCoords &a, const LocationCoords &b) { + return (a.lat == b.lat) && (a.lon == b.lon); +} +inline bool operator<(const LocationCoords &a, const LocationCoords &b) { + return (a.lat < b.lat) || ((a.lat == b.lat) && (a.lon < b.lon)); +} +inline uint qHash(const LocationCoords &t, uint seed = 0) { + return qHash(QtPrivate::QHashCombine().operator()(qHash(t.lat), t.lon), seed); +} +class LocationClickHandler : public TextClickHandler { public: - - EmailLink(const QString &email) : _email(email) { + LocationClickHandler(const LocationCoords &coords) : _coords(coords) { + setup(); } + QString copyToClipboardContextItem() const override; - const QString &text() const { - return _email; + QString text() const override { + return _text; } + void onClick(Qt::MouseButton button) const override; - void onClick(Qt::MouseButton button) const; - - const QString &readable() const { - return _email; - } - - QString encoded() const { - return _email; +protected: + QString url() const override { + return _text; } private: - QString _email; + void setup(); + LocationCoords _coords; + QString _text; }; -class MentionLink : public ITextLink { - TEXT_LINK_CLASS(MentionLink) - +class MentionClickHandler : public TextClickHandler { public: - - MentionLink(const QString &tag) : _tag(tag) { + MentionClickHandler(const QString &tag) : _tag(tag) { } + QString copyToClipboardContextItem() const override; - const QString &text() const { + QString text() const override { return _tag; } + void onClick(Qt::MouseButton button) const override; - void onClick(Qt::MouseButton button) const; - - const QString &readable() const { - return _tag; - } - - QString encoded() const { +protected: + QString url() const override { return _tag; } private: - QString _tag; }; -class HashtagLink : public ITextLink { - TEXT_LINK_CLASS(HashtagLink) - +class HashtagClickHandler : public TextClickHandler { public: - - HashtagLink(const QString &tag) : _tag(tag) { + HashtagClickHandler(const QString &tag) : _tag(tag) { } + QString copyToClipboardContextItem() const override; - const QString &text() const { + QString text() const override { return _tag; } + void onClick(Qt::MouseButton button) const override; - void onClick(Qt::MouseButton button) const; - - const QString &readable() const { - return _tag; - } - - QString encoded() const { +protected: + QString url() const override { return _tag; } private: - QString _tag; }; -class BotCommandLink : public ITextLink { - TEXT_LINK_CLASS(BotCommandLink) - +class BotCommandClickHandler : public TextClickHandler { public: - - BotCommandLink(const QString &cmd) : _cmd(cmd) { + BotCommandClickHandler(const QString &cmd) : _cmd(cmd) { } - - const QString &text() const { + QString text() const override { return _cmd; } + void onClick(Qt::MouseButton button) const override; - void onClick(Qt::MouseButton button) const; - - const QString &readable() const { - return _cmd; - } - - QString encoded() const { +protected: + QString url() const override { return _cmd; } private: - QString _cmd; }; @@ -515,11 +655,13 @@ enum TextCommands { TextCommandNoItalic = 0x04, TextCommandUnderline = 0x05, TextCommandNoUnderline = 0x06, - TextCommandLinkIndex = 0x07, // 0 - NoLink - TextCommandLinkText = 0x08, - TextCommandColor = 0x09, - TextCommandNoColor = 0x0A, - TextCommandSkipBlock = 0x0B, + TextCommandSemibold = 0x07, + TextCommandNoSemibold = 0x08, + TextCommandLinkIndex = 0x09, // 0 - NoLink + TextCommandLinkText = 0x0A, + TextCommandColor = 0x0B, + TextCommandNoColor = 0x0C, + TextCommandSkipBlock = 0x0D, TextCommandLangTag = 0x20, }; @@ -547,14 +689,17 @@ public: Text(int32 minResizeWidth = QFIXED_MAX); Text(style::font font, const QString &text, const TextParseOptions &options = _defaultOptions, int32 minResizeWidth = QFIXED_MAX, bool richText = false); Text(const Text &other); + Text(Text &&other); Text &operator=(const Text &other); + Text &operator=(Text &&other); + int32 countWidth(int32 width) const; int32 countHeight(int32 width) const; void setText(style::font font, const QString &text, const TextParseOptions &options = _defaultOptions); void setRichText(style::font font, const QString &text, TextParseOptions options = _defaultOptions, const TextCustomTagsMap &custom = TextCustomTagsMap()); void setMarkedText(style::font font, const QString &text, const EntitiesInText &entities, const TextParseOptions &options = _defaultOptions); - void setLink(uint16 lnkIndex, const TextLinkPtr &lnk); + void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); bool hasLinks() const; bool hasSkipBlock() const { @@ -573,27 +718,27 @@ public: void replaceFont(style::font f); // does not recount anything, use at your own risk! void draw(QPainter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, uint16 selectedFrom = 0, uint16 selectedTo = 0) const; - void drawElided(QPainter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0) const; + void drawElided(QPainter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false) const; void drawLeft(QPainter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, uint16 selectedFrom = 0, uint16 selectedTo = 0) const { draw(p, rtl() ? (outerw - left - width) : left, top, width, align, yFrom, yTo, selectedFrom, selectedTo); } - void drawLeftElided(QPainter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0) const { - drawElided(p, rtl() ? (outerw - left - width) : left, top, width, lines, align, yFrom, yTo, removeFromEnd); + void drawLeftElided(QPainter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false) const { + drawElided(p, rtl() ? (outerw - left - width) : left, top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere); } void drawRight(QPainter &p, int32 right, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, uint16 selectedFrom = 0, uint16 selectedTo = 0) const { draw(p, rtl() ? right : (outerw - right - width), top, width, align, yFrom, yTo, selectedFrom, selectedTo); } - void drawRightElided(QPainter &p, int32 right, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0) const { - drawElided(p, rtl() ? right : (outerw - right - width), top, width, lines, align, yFrom, yTo, removeFromEnd); + void drawRightElided(QPainter &p, int32 right, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false) const { + drawElided(p, rtl() ? right : (outerw - right - width), top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere); } - const TextLinkPtr &link(int32 x, int32 y, int32 width, style::align align = style::al_left) const; - const TextLinkPtr &linkLeft(int32 x, int32 y, int32 width, int32 outerw, style::align align = style::al_left) const { + const ClickHandlerPtr &link(int32 x, int32 y, int32 width, style::align align = style::al_left) const; + const ClickHandlerPtr &linkLeft(int32 x, int32 y, int32 width, int32 outerw, style::align align = style::al_left) const { return link(rtl() ? (outerw - x - width) : x, y, width, align); } - void getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y, int32 width, style::align align = style::al_left) const; - void getStateLeft(TextLinkPtr &lnk, bool &inText, int32 x, int32 y, int32 width, int32 outerw, style::align align = style::al_left) const { - return getState(lnk, inText, rtl() ? (outerw - x - width) : x, y, width, align); + void getState(ClickHandlerPtr &lnk, bool &inText, int32 x, int32 y, int32 width, style::align align = style::al_left, bool breakEverywhere = false) const; + void getStateLeft(ClickHandlerPtr &lnk, bool &inText, int32 x, int32 y, int32 width, int32 outerw, style::align align = style::al_left, bool breakEverywhere = false) const { + return getState(lnk, inText, rtl() ? (outerw - x - width) : x, y, width, align, breakEverywhere); } void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y, int32 width, style::align align = style::al_left) const; void getSymbolLeft(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y, int32 width, int32 outerw, style::align align = style::al_left) const { @@ -634,15 +779,19 @@ public: return true; } - void clean(); + void clear(); ~Text() { - clean(); + clear(); } private: void recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir = Qt::LayoutDirectionAuto); + // clear() deletes all blocks and calls this method + // it is also called from move constructor / assignment operator + void clearFields(); + QFixed _minResizeWidth, _maxWidth; int32 _minHeight; @@ -652,7 +801,7 @@ private: typedef QVector TextBlocks; TextBlocks _blocks; - typedef QVector TextLinks; + typedef QVector TextLinks; TextLinks _links; Qt::LayoutDirection _startDir; @@ -679,15 +828,6 @@ inline void textstyleRestore() { textstyleSet(0); } -// textlnk -void textlnkOver(const TextLinkPtr &lnk); -const TextLinkPtr &textlnkOver(); - -void textlnkDown(const TextLinkPtr &lnk); -const TextLinkPtr &textlnkDown(); - -bool textlnkDrawOver(const TextLinkPtr &lnk); - // textcmd QString textcmdSkipBlock(ushort w, ushort h); QString textcmdStartLink(ushort lnkIndex); @@ -697,6 +837,8 @@ QString textcmdLink(ushort lnkIndex, const QString &text); QString textcmdLink(const QString &url, const QString &text); QString textcmdStartColor(const style::color &color); QString textcmdStopColor(); +QString textcmdStartSemibold(); +QString textcmdStopSemibold(); const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true); inline bool chIsSpace(QChar ch, bool rich = false) { diff --git a/Telegram/SourceFiles/gui/twidget.cpp b/Telegram/SourceFiles/gui/twidget.cpp index edd21afcd6..eced0da475 100644 --- a/Telegram/SourceFiles/gui/twidget.cpp +++ b/Telegram/SourceFiles/gui/twidget.cpp @@ -16,11 +16,27 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "application.h" +#include "window.h" + +namespace Fonts { + + bool Started = false; + void start() { + if (!Started) { + Started = true; + + QFontDatabase::addApplicationFont(qsl(":/gui/art/fonts/OpenSans-Regular.ttf")); + QFontDatabase::addApplicationFont(qsl(":/gui/art/fonts/OpenSans-Bold.ttf")); + QFontDatabase::addApplicationFont(qsl(":/gui/art/fonts/OpenSans-Semibold.ttf")); + } + } + +} namespace { void _sendResizeEvents(QWidget *target) { @@ -37,6 +53,10 @@ namespace { } } +bool TWidget::inFocusChain() const { + return !isHidden() && App::wnd() && (App::wnd()->focusWidget() == this || isAncestorOf(App::wnd()->focusWidget())); +} + void myEnsureResized(QWidget *target) { if (target && (target->testAttribute(Qt::WA_PendingResizeEvent) || !target->testAttribute(Qt::WA_WState_Created))) { _sendResizeEvents(target); @@ -44,7 +64,7 @@ void myEnsureResized(QWidget *target) { } QPixmap myGrab(TWidget *target, QRect rect) { - myEnsureResized(target); + myEnsureResized(target); if (rect.isNull()) rect = target->rect(); QPixmap result(rect.size() * cRetinaFactor()); diff --git a/Telegram/SourceFiles/gui/twidget.h b/Telegram/SourceFiles/gui/twidget.h index d01504c2b7..4667e43a9e 100644 --- a/Telegram/SourceFiles/gui/twidget.h +++ b/Telegram/SourceFiles/gui/twidget.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -24,6 +24,10 @@ namespace App { const QPixmap &sprite(); } +namespace Fonts { + void start(); +} + class Painter : public QPainter { public: explicit Painter(QPaintDevice *device) : QPainter(device) { @@ -179,6 +183,8 @@ public: virtual void grabFinish() { } + bool inFocusChain() const; + private: }; @@ -198,3 +204,28 @@ private: const style::color &_color; }; + +class SingleDelayedCall : public QObject { + Q_OBJECT + +public: + SingleDelayedCall(QObject *parent, const char *member) : QObject(parent), _pending(false), _member(member) { + } + void call() { + if (!_pending) { + _pending = true; + QMetaObject::invokeMethod(this, "makeDelayedCall", Qt::QueuedConnection); + } + } + +private slots: + void makeDelayedCall() { + _pending = false; + QMetaObject::invokeMethod(parent(), _member); + } + +private: + bool _pending; + const char *_member; + +}; diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 8adc6896bc..780a625752 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -16,10 +16,10 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" +#include "gui/style.h" #include "lang.h" #include "mainwidget.h" @@ -75,23 +75,11 @@ namespace { _webpageDescriptionOptions.maxh = st::webPageDescriptionFont->height * 3; } - inline HistoryReply *toHistoryReply(HistoryItem *item) { - return item ? item->toHistoryReply() : 0; - } - inline const HistoryReply *toHistoryReply(const HistoryItem *item) { - return item ? item->toHistoryReply() : 0; - } - inline HistoryForwarded *toHistoryForwarded(HistoryItem *item) { - return item ? item->toHistoryForwarded() : 0; - } - inline const HistoryForwarded *toHistoryForwarded(const HistoryItem *item) { - return item ? item->toHistoryForwarded() : 0; - } inline const TextParseOptions &itemTextOptions(HistoryItem *item) { - return itemTextOptions(item->history(), item->from()); + return itemTextOptions(item->history(), item->author()); } inline const TextParseOptions &itemTextNoMonoOptions(const HistoryItem *item) { - return itemTextNoMonoOptions(item->history(), item->from()); + return itemTextNoMonoOptions(item->history(), item->author()); } } @@ -104,11 +92,8 @@ void DialogRow::paint(Painter &p, int32 w, bool act, bool sel, bool onlyBackgrou p.fillRect(fullRect, (act ? st::dlgActiveBG : (sel ? st::dlgHoverBG : st::dlgBG))->b); if (onlyBackground) return; - if (history->peer->migrateTo()) { - p.drawPixmap(st::dlgPaddingHor, st::dlgPaddingVer, history->peer->migrateTo()->photo->pix(st::dlgPhotoSize)); - } else { - p.drawPixmap(st::dlgPaddingHor, st::dlgPaddingVer, history->peer->photo->pix(st::dlgPhotoSize)); - } + PeerData *userpicPeer = (history->peer->migrateTo() ? history->peer->migrateTo() : history->peer); + userpicPeer->paintUserpicLeft(p, st::dlgPhotoSize, st::dlgPaddingHor, st::dlgPaddingVer, w); int32 nameleft = st::dlgPaddingHor + st::dlgPhotoSize + st::dlgPhotoPadding; int32 namewidth = w - nameleft - st::dlgPaddingHor; @@ -181,7 +166,7 @@ void DialogRow::paint(Painter &p, int32 w, bool act, bool sel, bool onlyBackgrou int32 unreadRectLeft = w - st::dlgPaddingHor - unreadRectWidth; int32 unreadRectTop = st::dlgHeight - st::dlgPaddingVer - unreadRectHeight; lastWidth -= unreadRectWidth + st::dlgUnreadPaddingHor; - p.setBrush((act ? st::dlgActiveUnreadBG : (history->mute ? st::dlgUnreadMutedBG : st::dlgUnreadBG))->b); + p.setBrush((act ? (history->mute ? st::dlgActiveUnreadMutedBG : st::dlgActiveUnreadBG) : (history->mute ? st::dlgUnreadMutedBG : st::dlgUnreadBG))->b); p.setPen(Qt::NoPen); p.drawRoundedRect(unreadRectLeft, unreadRectTop, unreadRectWidth, unreadRectHeight, st::dlgUnreadRadius, st::dlgUnreadRadius); p.setFont(st::dlgUnreadFont->f); @@ -211,11 +196,8 @@ void FakeDialogRow::paint(Painter &p, int32 w, bool act, bool sel, bool onlyBack if (onlyBackground) return; History *history = _item->history(); - if (history->peer->migrateTo()) { - p.drawPixmap(st::dlgPaddingHor, st::dlgPaddingVer, history->peer->migrateTo()->photo->pix(st::dlgPhotoSize)); - } else { - p.drawPixmap(st::dlgPaddingHor, st::dlgPaddingVer, history->peer->photo->pix(st::dlgPhotoSize)); - } + PeerData *userpicPeer = (history->peer->migrateTo() ? history->peer->migrateTo() : history->peer); + userpicPeer->paintUserpicLeft(p, st::dlgPhotoSize, st::dlgPaddingHor, st::dlgPaddingVer, w); int32 nameleft = st::dlgPaddingHor + st::dlgPhotoSize + st::dlgPhotoPadding; int32 namewidth = w - nameleft - st::dlgPaddingHor; @@ -280,16 +262,17 @@ History::History(const PeerId &peerId) : width(0), height(0) , unreadCount(0) , inboxReadBefore(1) , outboxReadBefore(1) -, showFrom(0) -, unreadBar(0) +, showFrom(nullptr) +, unreadBar(nullptr) , peer(App::peer(peerId)) , oldLoaded(false) , newLoaded(true) , lastMsg(0) -, draftToId(0) -, lastWidth(0) -, lastScrollTop(ScrollMax) -, lastShowAtMsgId(ShowAtUnreadMsgId) +, msgDraft(0) +, editDraft(0) +, showAtMsgId(ShowAtUnreadMsgId) +, scrollTopItem(nullptr) +, scrollTopOffset(0) , mute(isNotifyMuted(peer->notify)) , lastKeyboardInited(false) , lastKeyboardUsed(false) @@ -299,14 +282,13 @@ History::History(const PeerId &peerId) : width(0), height(0) , sendRequestId(0) , textCachedFor(0) , lastItemTextCache(st::dlgRichMinWidth) -, posInDialogs(0) , typingText(st::dlgRichMinWidth) -{ +, _sortKeyInChatList(0) { if (peer->isChannel() || (peer->isUser() && peer->asUser()->botInfo)) { outboxReadBefore = INT_MAX; } - for (int32 i = 0; i < OverviewCount; ++i) { - overviewCountData[i] = -1; // not loaded yet + for (auto &countData : overviewCountData) { + countData = -1; // not loaded yet } } @@ -321,8 +303,21 @@ void History::clearLastKeyboard() { lastKeyboardFrom = 0; } -bool History::updateTyping(uint64 ms, uint32 dots, bool force) { - if (!ms) ms = getms(true); +bool History::canHaveFromPhotos() const { + if (peer->isUser() && !Adaptive::Wide()) { + return false; + } else if (isChannel() && asChannelHistory()->onlyImportant()) { + return false; + } + return true; +} + +void History::setHasPendingResizedItems() { + _flags |= Flag::f_has_pending_resized_items; + Global::RefHandleHistoryUpdate().call(); +} + +bool History::updateTyping(uint64 ms, bool force) { bool changed = force; for (TypingUsers::iterator i = typing.begin(), e = typing.end(); i != e;) { if (ms >= i.value()) { @@ -353,8 +348,8 @@ bool History::updateTyping(uint64 ms, uint32 dots, bool force) { switch (sendActions.begin().value().type) { case SendActionRecordVideo: newTypingStr = peer->isUser() ? lang(lng_send_action_record_video) : lng_user_action_record_video(lt_user, sendActions.begin().key()->firstName); break; case SendActionUploadVideo: newTypingStr = peer->isUser() ? lang(lng_send_action_upload_video) : lng_user_action_upload_video(lt_user, sendActions.begin().key()->firstName); break; - case SendActionRecordAudio: newTypingStr = peer->isUser() ? lang(lng_send_action_record_audio) : lng_user_action_record_audio(lt_user, sendActions.begin().key()->firstName); break; - case SendActionUploadAudio: newTypingStr = peer->isUser() ? lang(lng_send_action_upload_audio) : lng_user_action_upload_audio(lt_user, sendActions.begin().key()->firstName); break; + case SendActionRecordVoice: newTypingStr = peer->isUser() ? lang(lng_send_action_record_audio) : lng_user_action_record_audio(lt_user, sendActions.begin().key()->firstName); break; + case SendActionUploadVoice: newTypingStr = peer->isUser() ? lang(lng_send_action_upload_audio) : lng_user_action_upload_audio(lt_user, sendActions.begin().key()->firstName); break; case SendActionUploadPhoto: newTypingStr = peer->isUser() ? lang(lng_send_action_upload_photo) : lng_user_action_upload_photo(lt_user, sendActions.begin().key()->firstName); break; case SendActionUploadFile: newTypingStr = peer->isUser() ? lang(lng_send_action_upload_file) : lng_user_action_upload_file(lt_user, sendActions.begin().key()->firstName); break; case SendActionChooseLocation: newTypingStr = peer->isUser() ? lang(lng_send_action_geo_location) : lng_user_action_geo_location(lt_user, sendActions.begin().key()->firstName); break; @@ -369,12 +364,12 @@ bool History::updateTyping(uint64 ms, uint32 dots, bool force) { } } if (!typingStr.isEmpty()) { - if (typingText.lastDots(dots % 4)) { + if (typingText.lastDots(typingDots % 4)) { changed = true; } } if (changed && App::main()) { - if (!dialogs.isEmpty()) App::main()->dlgUpdated(dialogs[0]); + updateChatListEntry(); if (App::main()->historyPeer() == peer) { App::main()->topBar()->update(); } @@ -382,11 +377,13 @@ bool History::updateTyping(uint64 ms, uint32 dots, bool force) { return changed; } -ChannelHistory::ChannelHistory(const PeerId &peer) : History(peer), -unreadCountAll(0), -_onlyImportant(!isMegagroup()), -_otherOldLoaded(false), _otherNewLoaded(true), -_collapseMessage(0), _joinedMessage(0) { +ChannelHistory::ChannelHistory(const PeerId &peer) : History(peer) +, unreadCountAll(0) +, _onlyImportant(!isMegagroup()) +, _otherOldLoaded(false) +, _otherNewLoaded(true) +, _collapseMessage(nullptr) +, _joinedMessage(nullptr) { } bool ChannelHistory::isSwitchReadyFor(MsgId switchId, MsgId &fixInScrollMsgId, int32 &fixInScrollMsgTop) { @@ -480,13 +477,9 @@ void ChannelHistory::getSwitchReadyFor(MsgId switchId, MsgId &fixInScrollMsgId, switchMode(); } else { clear(true); - newLoaded = oldLoaded = false; - lastWidth = 0; } } else { clear(true); - newLoaded = oldLoaded = false; - lastWidth = 0; } } else { _otherList.clear(); @@ -501,13 +494,13 @@ void ChannelHistory::insertCollapseItem(MsgId wasMinId) { if (_onlyImportant || isMegagroup()) return; bool insertAfter = false; - for (int32 blockIndex = 1, blocksCount = blocks.size(); blockIndex < blocksCount; ++blockIndex) { // skip first date block + for (int32 blockIndex = 0, blocksCount = blocks.size(); blockIndex < blocksCount; ++blockIndex) { HistoryBlock *block = blocks.at(blockIndex); for (int32 itemIndex = 0, itemsCount = block->items.size(); itemIndex < itemsCount; ++itemIndex) { HistoryItem *item = block->items.at(itemIndex); if (insertAfter || item->id > wasMinId || (item->id == wasMinId && !item->isImportant())) { - _collapseMessage = new HistoryCollapse(this, block, wasMinId, item->date); - if (!addNewInTheMiddle(regItem(_collapseMessage), blockIndex, itemIndex)) { + _collapseMessage = HistoryCollapse::create((History*)this, wasMinId, item->date); + if (!addNewInTheMiddle(_collapseMessage, blockIndex, itemIndex)) { _collapseMessage = 0; } return; @@ -565,7 +558,7 @@ void ChannelHistory::getRangeDifferenceNext(int32 pts) { if (!App::main() || _rangeDifferenceToId < _rangeDifferenceFromId) return; int32 limit = _rangeDifferenceToId + 1 - _rangeDifferenceFromId; - _rangeDifferenceRequestId = MTP::send(MTPupdates_GetChannelDifference(peer->asChannel()->inputChannel, MTP_channelMessagesFilter(MTP_int(0), MTP_vector(1, MTP_messageRange(MTP_int(_rangeDifferenceFromId), MTP_int(_rangeDifferenceToId)))), MTP_int(pts), MTP_int(limit)), App::main()->rpcDone(&MainWidget::gotRangeDifference, peer->asChannel())); + _rangeDifferenceRequestId = MTP::send(MTPupdates_GetChannelDifference(peer->asChannel()->inputChannel, MTP_channelMessagesFilter(MTP_flags(MTPDchannelMessagesFilter::Flags(0)), MTP_vector(1, MTP_messageRange(MTP_int(_rangeDifferenceFromId), MTP_int(_rangeDifferenceToId)))), MTP_int(pts), MTP_int(limit)), App::main()->rpcDone(&MainWidget::gotRangeDifference, peer->asChannel())); } void ChannelHistory::addNewGroup(const MTPMessageGroup &group) { @@ -576,7 +569,7 @@ void ChannelHistory::addNewGroup(const MTPMessageGroup &group) { _otherNewLoaded = false; } else if (_otherNewLoaded) { if (_otherList.isEmpty() || _otherList.back()->type() != HistoryItemGroup) { - _otherList.push_back(regItem(new HistoryGroup(this, 0, d, _otherList.isEmpty() ? date(d.vdate) : _otherList.back()->date))); + _otherList.push_back(HistoryGroup::create(this, d, _otherList.isEmpty() ? date(d.vdate) : _otherList.back()->date)); } else { static_cast(_otherList.back())->uniteWith(d.vmin_id.v, d.vmax_id.v, d.vcount.v); } @@ -584,30 +577,8 @@ void ChannelHistory::addNewGroup(const MTPMessageGroup &group) { if (onlyImportant()) { if (newLoaded) { - HistoryItem *prev = blocks.isEmpty() ? 0 : blocks.back()->items.back(); - HistoryBlock *to = 0; - bool newBlock = blocks.isEmpty(); - if (newBlock) { - to = new HistoryBlock(this); - to->y = height; - } else { - to = blocks.back(); - height -= to->height; - } - prev = addMessageGroupAfterPrevToBlock(d, prev, to); - height += to->height; - if (newBlock) { - HistoryBlock *dateBlock = new HistoryBlock(this); - HistoryItem *dayItem = createDayServiceMsg(this, dateBlock, blocks.front()->items.front()->date); - dateBlock->items.push_back(dayItem); - int32 dh = dayItem->resize(width); - dateBlock->height = dh; - for (Blocks::iterator i = blocks.begin(), e = blocks.end(); i != e; ++i) { - (*i)->y += dh; - } - blocks.push_front(dateBlock); // date block CHECK - height += dh; - } + t_assert(!isBuildingFrontBlock()); + addMessageGroup(d); } } else { setNotLoadedAtBottom(); @@ -619,24 +590,25 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) { return _joinedMessage; } - UserData *inviter = (peer->asChannel()->inviter > 0) ? App::userLoaded(peer->asChannel()->inviter) : 0; - if (!inviter) return 0; + UserData *inviter = (peer->asChannel()->inviter > 0) ? App::userLoaded(peer->asChannel()->inviter) : nullptr; + if (!inviter) return nullptr; + + MTPDmessage::Flags flags = 0; + if (peerToUser(inviter->id) == MTP::authedId()) { + unread = false; + } else if (unread) { + flags |= MTPDmessage::Flag::f_unread; + } - if (peerToUser(inviter->id) == MTP::authedId()) unread = false; - int32 flags = (unread ? MTPDmessage::flag_unread : 0); QDateTime inviteDate = peer->asChannel()->inviteDate; if (unread) _maxReadMessageDate = inviteDate; if (isEmpty()) { - HistoryBlock *to = new HistoryBlock(this); - bool newBlock = true; - _joinedMessage = new HistoryJoined(this, to, inviteDate, inviter, flags); - if (!addNewItem(to, newBlock, regItem(_joinedMessage), unread)) { - _joinedMessage = 0; - } + _joinedMessage = HistoryJoined::create(this, inviteDate, inviter, flags); + addNewItem(_joinedMessage, unread); return _joinedMessage; } - HistoryItem *lastSeenDateItem = 0; - for (int32 blockIndex = blocks.size(); blockIndex > 1;) { + + for (int32 blockIndex = blocks.size(); blockIndex > 0;) { HistoryBlock *block = blocks.at(--blockIndex); for (int32 itemIndex = block->items.size(); itemIndex > 0;) { HistoryItem *item = block->items.at(--itemIndex); @@ -645,23 +617,12 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) { if (item->date <= inviteDate) { if (peer->isMegagroup() && peer->migrateFrom() && item->isGroupMigrate()) { peer->asChannel()->mgInfo->joinedMessageFound = true; - return 0; + return nullptr; } ++itemIndex; - if (item->date.date() != inviteDate.date()) { - HistoryDateMsg *joinedDateItem = new HistoryDateMsg(this, block, inviteDate.date()); - if (addNewInTheMiddle(regItem(joinedDateItem), blockIndex, itemIndex)) { - ++itemIndex; - } - } - _joinedMessage = new HistoryJoined(this, block, inviteDate, inviter, flags); - if (!addNewInTheMiddle(regItem(_joinedMessage), blockIndex, itemIndex)) { - _joinedMessage = 0; - } - if (lastSeenDateItem && lastSeenDateItem->date.date() == inviteDate.date()) { - lastSeenDateItem->destroy(); - } + _joinedMessage = HistoryJoined::create(this, inviteDate, inviter, flags); + addNewInTheMiddle(_joinedMessage, blockIndex, itemIndex); if (lastMsgDate.isNull() || inviteDate >= lastMsgDate) { setLastMessage(_joinedMessage); if (unread) { @@ -669,76 +630,18 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) { } } return _joinedMessage; - } else { - lastSeenDateItem = 0; } - } else if (type == HistoryItemDate) { - lastSeenDateItem = item; } } } - // adding new item to new block - int32 addToH = 0, skip = 0; - if (!blocks.isEmpty()) { // remove date block - if (width) addToH = -blocks.front()->height; - delete blocks.front(); - blocks.pop_front(); - } - HistoryItem *till = blocks.isEmpty() ? 0 : blocks.front()->items.front(); + startBuildingFrontBlock(); - HistoryBlock *block = new HistoryBlock(this); + _joinedMessage = HistoryJoined::create(this, inviteDate, inviter, flags); + addItemToBlock(_joinedMessage); + + finishBuildingFrontBlock(); - _joinedMessage = new HistoryJoined(this, block, inviteDate, inviter, flags); - addItemAfterPrevToBlock(regItem(_joinedMessage), 0, block); - if (till && _joinedMessage && inviteDate.date() != till->date.date()) { - HistoryItem *dayItem = createDayServiceMsg(this, block, till->date); - block->items.push_back(dayItem); - if (width) { - dayItem->y = block->height; - block->height += dayItem->resize(width); - } - } - if (!block->items.isEmpty()) { - blocks.push_front(block); // CHECK - if (width) { - addToH += block->height; - ++skip; - } - } else { - delete block; - } - if (!blocks.isEmpty()) { - HistoryBlock *dateBlock = new HistoryBlock(this); - HistoryItem *dayItem = createDayServiceMsg(this, dateBlock, blocks.front()->items.front()->date); - dateBlock->items.push_back(dayItem); - if (width) { - int32 dh = dayItem->resize(width); - dateBlock->height = dh; - if (skip) { - blocks.front()->y += dh; - } - addToH += dh; - ++skip; - } - blocks.push_front(dateBlock); // date block CHECK - } - if (width && addToH) { - for (Blocks::iterator i = blocks.begin(), e = blocks.end(); i != e; ++i) { - if (skip) { - --skip; - } else { - (*i)->y += addToH; - } - } - height += addToH; - } - if (!lastMsgDate.isNull() && inviteDate >= lastMsgDate) { - setLastMessage(_joinedMessage); - if (unread) { - newItemAdded(_joinedMessage); - } - } return _joinedMessage; } @@ -759,9 +662,9 @@ void ChannelHistory::checkJoinedMessage(bool createUnread) { QDateTime inviteDate = peer->asChannel()->inviteDate; QDateTime firstDate, lastDate; - for (int32 blockIndex = 1, blocksCount = blocks.size(); blockIndex < blocksCount; ++blockIndex) { + for (int blockIndex = 0, blocksCount = blocks.size(); blockIndex < blocksCount; ++blockIndex) { HistoryBlock *block = blocks.at(blockIndex); - int32 itemIndex = 0, itemsCount = block->items.size(); + int itemIndex = 0, itemsCount = block->items.size(); for (; itemIndex < itemsCount; ++itemIndex) { HistoryItem *item = block->items.at(itemIndex); HistoryItemType type = item->type(); @@ -772,9 +675,9 @@ void ChannelHistory::checkJoinedMessage(bool createUnread) { } if (itemIndex < itemsCount) break; } - for (int32 blockIndex = blocks.size(); blockIndex > 1;) { + for (int blockIndex = blocks.size(); blockIndex > 0;) { HistoryBlock *block = blocks.at(--blockIndex); - int32 itemIndex = block->items.size(); + int itemIndex = block->items.size(); for (; itemIndex > 0;) { HistoryItem *item = block->items.at(--itemIndex); HistoryItemType type = item->type(); @@ -800,9 +703,9 @@ void ChannelHistory::checkJoinedMessage(bool createUnread) { void ChannelHistory::checkMaxReadMessageDate() { if (_maxReadMessageDate.isValid()) return; - for (int32 blockIndex = blocks.size(); blockIndex > 0;) { + for (int blockIndex = blocks.size(); blockIndex > 0;) { HistoryBlock *block = blocks.at(--blockIndex); - for (int32 itemIndex = block->items.size(); itemIndex > 0;) { + for (int itemIndex = block->items.size(); itemIndex > 0;) { HistoryItem *item = block->items.at(--itemIndex); if ((item->isImportant() || isMegagroup()) && !item->unread()) { _maxReadMessageDate = item->date; @@ -846,33 +749,28 @@ HistoryItem *ChannelHistory::addNewToBlocks(const MTPMessage &msg, NewMessageTyp } if (!isImportant && onlyImportant()) { - HistoryItem *item = addToHistory(msg), *prev = isEmpty() ? 0 : blocks.back()->items.back(); - HistoryItem *group = addMessageGroupAfterPrev(item, prev); - if (group && group != prev) { - height += group->height(); - } + HistoryItem *item = addToHistory(msg); + + t_assert(!isBuildingFrontBlock()); + addMessageGroup([item, this](HistoryItem *previous) -> HistoryGroup* { // create(..) + return HistoryGroup::create(this, item, previous ? previous->date : item->date); + }, [item](HistoryGroup *existing) { // unite(..) + existing->uniteWith(item); + }); + return item; } + // when we are receiving channel dialog rows we get one important and one not important + // message for each history, adding all of them with type == NewMessageLast + // if we get a second (not important) message of this two we need to clear the history + // because a lot of messages in between those two are skipped if (!isImportantFlags && !onlyImportant() && !isEmpty() && type == NewMessageLast) { clear(true); + newLoaded = true; // adding the last message } - HistoryBlock *to = 0; - bool newBlock = blocks.isEmpty(); - if (newBlock) { - to = new HistoryBlock(this); - } else { - to = blocks.back(); - } - HistoryItem *item = createItem((type == NewMessageLast) ? 0 : to, msg, (type == NewMessageUnread)); - if (type == NewMessageLast) { - if (!item->detached()) { - return item; - } - item->attach(to); - } - return addNewItem(to, newBlock, item, (type == NewMessageUnread)); + return addNewToLastBlock(msg, type); } void ChannelHistory::addNewToOther(HistoryItem *item, NewMessageType type) { @@ -886,7 +784,7 @@ void ChannelHistory::addNewToOther(HistoryItem *item, NewMessageType type) { } } else { if (_otherList.isEmpty() || _otherList.back()->type() != HistoryItemGroup) { - _otherList.push_back(regItem(new HistoryGroup(this, 0, item, _otherList.isEmpty() ? item->date : _otherList.back()->date))); + _otherList.push_back(HistoryGroup::create(this, item, _otherList.isEmpty() ? item->date : _otherList.back()->date)); } else { static_cast(_otherList.back())->uniteWith(item); } @@ -901,11 +799,9 @@ void ChannelHistory::switchMode() { OtherList savedList; if (!blocks.isEmpty()) { - savedList.reserve(((blocks.size() - 2) * MessagesPerPage + blocks.back()->items.size()) * (onlyImportant() ? 2 : 1)); - for (Blocks::const_iterator i = blocks.cbegin(), e = blocks.cend(); i != e; ++i) { - HistoryBlock *block = *i; - for (HistoryBlock::Items::const_iterator j = block->items.cbegin(), end = block->items.cend(); j != end; ++j) { - HistoryItem *item = *j; + savedList.reserve(((blocks.size() - 1) * MessagesPerPage + blocks.back()->items.size()) * (onlyImportant() ? 2 : 1)); + for_const (const HistoryBlock *block, blocks) { + for_const (HistoryItem *item, block->items) { HistoryItemType itemType = item->type(); if (itemType == HistoryItemMsg || itemType == HistoryItemGroup) { savedList.push_back(item); @@ -917,27 +813,15 @@ void ChannelHistory::switchMode() { clear(true); + t_assert(!isBuildingFrontBlock()); + newLoaded = _otherNewLoaded; oldLoaded = _otherOldLoaded; - if (int32 count = _otherList.size()) { - blocks.reserve(qCeil(count / float64(MessagesPerPage)) + 1); - createInitialDateBlock(_otherList.front()->date); - - HistoryItem *prev = 0; - for (int32 i = 0; i < count;) { - HistoryBlock *block = new HistoryBlock(this); - int32 willAddToBlock = qMin(int32(MessagesPerPage), count - i); - block->items.reserve(willAddToBlock); - for (int32 till = i + willAddToBlock; i < till; ++i) { - HistoryItem *item = _otherList.at(i); - item->attach(block); - prev = addItemAfterPrevToBlock(item, prev, block); - } - blocks.push_back(block); // CHECK - if (width) { - block->y = height; - height += block->height; - } + if (int count = _otherList.size()) { + blocks.reserve((count / MessagesPerPage) + 1); + for (int i = 0; i < count; ++i) { + t_assert(_otherList.at(i)->detached()); + addItemToBlock(_otherList.at(i)); } } @@ -947,7 +831,9 @@ void ChannelHistory::switchMode() { _onlyImportant = !_onlyImportant; - lastWidth = 0; + // scroll to the bottom if nothing special is intended + // (like scrolling to the collapse item) + scrollTopItem = nullptr; checkJoinedMessage(); } @@ -994,13 +880,13 @@ HistoryGroup *ChannelHistory::findGroup(MsgId msgId) const { // find message gro } HistoryBlock *ChannelHistory::findGroupBlock(MsgId msgId) const { // find block with message group using binary search - if (isEmpty()) return 0; + if (isEmpty()) return nullptr; - int32 blockIndex = 0; - if (blocks.size() > 1) for (int32 minBlock = 0, maxBlock = blocks.size();;) { - for (int32 startCheckBlock = (minBlock + maxBlock) / 2, checkBlock = startCheckBlock;;) { + int blockIndex = 0; + if (blocks.size() > 1) for (int minBlock = 0, maxBlock = blocks.size();;) { + for (int startCheckBlock = (minBlock + maxBlock) / 2, checkBlock = startCheckBlock;;) { HistoryBlock *block = blocks.at(checkBlock); - HistoryBlock::Items::const_iterator i = block->items.cbegin(), e = block->items.cend(); + auto i = block->items.cbegin(), e = block->items.cend(); for (; i != e; ++i) { // out msgs could be a mess in monotonic ids if (((*i)->id > 0 && !(*i)->out()) || (*i)->type() == HistoryItemGroup) { MsgId threshold = ((*i)->id > 0) ? (*i)->id : static_cast(*i)->minId(); @@ -1054,17 +940,17 @@ HistoryGroup *ChannelHistory::findGroupInOther(MsgId msgId) const { // find mess } } HistoryItem *item = _otherList.at(otherIndex); - if (item->type() != HistoryItemGroup) return 0; + if (item->type() != HistoryItemGroup) return nullptr; + HistoryGroup *result = static_cast(item); - return (result->minId() < msgId && result->maxId() > msgId) ? result : 0; + return (result->minId() < msgId && result->maxId() > msgId) ? result : nullptr; } HistoryItem *ChannelHistory::findPrevItem(HistoryItem *item) const { - if (item->detached()) return 0; - int32 itemIndex = item->block()->items.indexOf(item); - int32 blockIndex = blocks.indexOf(item->block()); - if (itemIndex < 0 || blockIndex < 0) return 0; + if (item->detached()) return nullptr; + int itemIndex = item->indexInBlock(); + int blockIndex = item->block()->indexInHistory(); for (++blockIndex, ++itemIndex; blockIndex > 0;) { --blockIndex; HistoryBlock *block = blocks.at(blockIndex); @@ -1076,14 +962,14 @@ HistoryItem *ChannelHistory::findPrevItem(HistoryItem *item) const { } } } - return 0; + return nullptr; } void ChannelHistory::messageDetached(HistoryItem *msg) { if (_collapseMessage == msg) { - _collapseMessage = 0; + _collapseMessage = nullptr; } else if (_joinedMessage == msg) { - _joinedMessage = 0; + _joinedMessage = nullptr; } } @@ -1112,12 +998,20 @@ void ChannelHistory::messageWithIdDeleted(MsgId msgId) { } } +ChannelHistory::~ChannelHistory() { + // all items must be destroyed before ChannelHistory is destroyed + // or they will call history()->asChannelHistory() -> undefined behaviour + clearOnDestroy(); +} + bool DialogsList::del(const PeerId &peerId, DialogRow *replacedBy) { RowByPeer::iterator i = rowByPeer.find(peerId); if (i == rowByPeer.cend()) return false; DialogRow *row = i.value(); - emit App::main()->dialogRowReplaced(row, replacedBy); + if (App::main()) { + emit App::main()->dialogRowReplaced(row, replacedBy); + } if (row == current) { current = row->next; @@ -1186,7 +1080,7 @@ void DialogsIndexed::peerNameChanged(PeerData *peer, const PeerData::Names &oldN } } for (PeerData::NameFirstChars::const_iterator i = toRemove.cbegin(), e = toRemove.cend(); i != e; ++i) { - if (sortMode == DialogsSortByDate) history->dialogs.remove(*i); + if (sortMode == DialogsSortByDate) history->removeChatListEntryByLetter(*i); DialogsIndex::iterator j = index.find(*i); if (j != index.cend()) { j.value()->del(peer->id, mainRow); @@ -1197,11 +1091,8 @@ void DialogsIndexed::peerNameChanged(PeerData *peer, const PeerData::Names &oldN if (j == index.cend()) { j = index.insert(*i, new DialogsList(sortMode)); } - if (sortMode == DialogsSortByDate) { - history->dialogs.insert(*i, j.value()->addToEnd(history)); - } else { - j.value()->addToEnd(history); - } + DialogRow *row = j.value()->addToEnd(history); + if (sortMode == DialogsSortByDate) history->addChatListEntryByLetter(*i, row); } } } @@ -1234,6 +1125,12 @@ void Histories::clear() { for (Map::const_iterator i = map.cbegin(), e = map.cend(); i != e; ++i) { delete i.value(); } + Global::RefPendingRepaintItems().clear(); + + _unreadFull = _unreadMuted = 0; + if (App::wnd()) { + App::wnd()->updateCounter(); + } App::historyClearItems(); typing.clear(); map.clear(); @@ -1245,13 +1142,13 @@ void Histories::regSendAction(History *history, UserData *user, const MTPSendMes return; } - uint64 ms = getms(true); + uint64 ms = getms(); switch (action.type()) { case mtpc_sendMessageTypingAction: history->typing[user] = ms + 6000; break; case mtpc_sendMessageRecordVideoAction: history->sendActions.insert(user, SendAction(SendActionRecordVideo, ms + 6000)); break; case mtpc_sendMessageUploadVideoAction: history->sendActions.insert(user, SendAction(SendActionUploadVideo, ms + 6000, action.c_sendMessageUploadVideoAction().vprogress.v)); break; - case mtpc_sendMessageRecordAudioAction: history->sendActions.insert(user, SendAction(SendActionRecordAudio, ms + 6000)); break; - case mtpc_sendMessageUploadAudioAction: history->sendActions.insert(user, SendAction(SendActionUploadAudio, ms + 6000, action.c_sendMessageUploadAudioAction().vprogress.v)); break; + case mtpc_sendMessageRecordAudioAction: history->sendActions.insert(user, SendAction(SendActionRecordVoice, ms + 6000)); break; + case mtpc_sendMessageUploadAudioAction: history->sendActions.insert(user, SendAction(SendActionUploadVoice, ms + 6000, action.c_sendMessageUploadAudioAction().vprogress.v)); break; case mtpc_sendMessageUploadPhotoAction: history->sendActions.insert(user, SendAction(SendActionUploadPhoto, ms + 6000, action.c_sendMessageUploadPhotoAction().vprogress.v)); break; case mtpc_sendMessageUploadDocumentAction: history->sendActions.insert(user, SendAction(SendActionUploadFile, ms + 6000, action.c_sendMessageUploadDocumentAction().vprogress.v)); break; case mtpc_sendMessageGeoLocationAction: history->sendActions.insert(user, SendAction(SendActionChooseLocation, ms + 6000)); break; @@ -1264,17 +1161,16 @@ void Histories::regSendAction(History *history, UserData *user, const MTPSendMes TypingHistories::const_iterator i = typing.find(history); if (i == typing.cend()) { typing.insert(history, ms); - history->typingFrame = 0; + history->typingDots = 0; + _a_typings.start(); } - - history->updateTyping(ms, history->typingFrame, true); - _a_typings.start(); + history->updateTyping(ms, true); } void Histories::step_typings(uint64 ms, bool timer) { for (TypingHistories::iterator i = typing.begin(), e = typing.end(); i != e;) { - uint32 typingFrame = (ms - i.value()) / 150; - i.key()->updateTyping(ms, typingFrame); + i.key()->typingDots = (ms - i.value()) / 150; + i.key()->updateTyping(ms); if (i.key()->typing.isEmpty() && i.key()->sendActions.isEmpty()) { i = typing.erase(i); } else { @@ -1302,25 +1198,22 @@ HistoryItem *Histories::addNewMessage(const MTPMessage &msg, NewMessageType type return findOrInsert(peer, 0, 0)->addNewMessage(msg, type); } -HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, bool applyServiceAction) { +HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) { MsgId msgId = 0; switch (msg.type()) { case mtpc_messageEmpty: msgId = msg.c_messageEmpty().vid.v; break; case mtpc_message: msgId = msg.c_message().vid.v; break; case mtpc_messageService: msgId = msg.c_messageService().vid.v; break; } - if (!msgId) return 0; + if (!msgId) return nullptr; HistoryItem *result = App::histItemById(channelId(), msgId); if (result) { - if (block) { - if (!result->detached()) { - result->detach(); - } - result->attach(block); + if (!result->detached() && detachExistingItem) { + result->detach(); } if (msg.type() == mtpc_message) { - result->updateMedia(msg.c_message().has_media() ? (&msg.c_message().vmedia) : 0, (block ? false : true)); + result->updateMedia(msg.c_message().has_media() ? (&msg.c_message().vmedia) : 0); if (applyServiceAction) { App::checkSavedGif(result); } @@ -1330,7 +1223,7 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo switch (msg.type()) { case mtpc_messageEmpty: - result = new HistoryServiceMsg(this, block, msg.c_messageEmpty().vid.v, date(), lang(lng_message_empty)); + result = HistoryService::create(this, msg.c_messageEmpty().vid.v, date(), lang(lng_message_empty)); break; case mtpc_message: { @@ -1360,20 +1253,6 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo default: badMedia = 1; break; } break; - case mtpc_messageMediaVideo: - switch (m.vmedia.c_messageMediaVideo().vvideo.type()) { - case mtpc_video: break; - case mtpc_videoEmpty: badMedia = 2; break; - default: badMedia = 1; break; - } - break; - case mtpc_messageMediaAudio: - switch (m.vmedia.c_messageMediaAudio().vaudio.type()) { - case mtpc_audio: break; - case mtpc_audioEmpty: badMedia = 2; break; - default: badMedia = 1; break; - } - break; case mtpc_messageMediaDocument: switch (m.vmedia.c_messageMediaDocument().vdocument.type()) { case mtpc_document: break; @@ -1392,27 +1271,21 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo case mtpc_messageMediaUnsupported: default: badMedia = 1; break; } - if (false && badMedia == 1) { -// QString text(lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org"))); + if (badMedia == 1) { + QString text(lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org"))); + EntitiesInText entities = textParseEntities(text, _historyTextNoMonoOptions.flags); + entities.push_front(EntityInText(EntityInTextItalic, 0, text.size())); + result = HistoryMessage::create(this, m.vid.v, m.vflags.v, m.vreply_to_msg_id.v, m.vvia_bot_id.v, date(m.vdate), m.vfrom_id.v, text, entities); } else if (badMedia) { - result = new HistoryServiceMsg(this, block, m.vid.v, date(m.vdate), lang((badMedia == 2) ? lng_message_empty : lng_media_unsupported), m.vflags.v, 0, m.has_from_id() ? m.vfrom_id.v : 0); + result = HistoryService::create(this, m.vid.v, date(m.vdate), lang(lng_message_empty), m.vflags.v, m.has_from_id() ? m.vfrom_id.v : 0); } else { - if ((m.has_fwd_date() && m.vfwd_date.v > 0) || (m.has_fwd_from_id() && peerFromMTP(m.vfwd_from_id) != 0)) { - result = new HistoryForwarded(this, block, m); - } else if (m.has_reply_to_msg_id() && m.vreply_to_msg_id.v > 0) { - result = new HistoryReply(this, block, m); - } else { - result = new HistoryMessage(this, block, m); - } - if (m.has_reply_markup()) { - App::feedReplyMarkup(channelId(), msgId, m.vreply_markup); - } + result = HistoryMessage::create(this, m); } } break; case mtpc_messageService: { const MTPDmessageService &d(msg.c_messageService()); - result = new HistoryServiceMsg(this, block, d); + result = HistoryService::create(this, d); if (applyServiceAction) { const MTPmessageAction &action(d.vaction); @@ -1428,7 +1301,7 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated; } if (user->botInfo) { - peer->asChannel()->mgInfo->bots.insert(user, true); + peer->asChannel()->mgInfo->bots.insert(user); if (peer->asChannel()->mgInfo->botStatus != 0 && peer->asChannel()->mgInfo->botStatus < 2) { peer->asChannel()->mgInfo->botStatus = 2; } @@ -1446,7 +1319,7 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo peer->asChannel()->mgInfo->lastParticipants.push_front(result->from()->asUser()); } if (result->from()->asUser()->botInfo) { - peer->asChannel()->mgInfo->bots.insert(result->from()->asUser(), true); + peer->asChannel()->mgInfo->bots.insert(result->from()->asUser()); if (peer->asChannel()->mgInfo->botStatus != 0 && peer->asChannel()->mgInfo->botStatus < 2) { peer->asChannel()->mgInfo->botStatus = 2; } @@ -1473,7 +1346,18 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo if (index >= 0) { peer->asChannel()->mgInfo->lastParticipants.removeAt(index); } - peer->asChannel()->mgInfo->lastAdmins.remove(user); + if (peer->asChannel()->count > 1) { + --peer->asChannel()->count; + } else { + peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; + peer->asChannel()->mgInfo->lastParticipantsCount = 0; + } + if (peer->asChannel()->mgInfo->lastAdmins.contains(user)) { + peer->asChannel()->mgInfo->lastAdmins.remove(user); + if (peer->asChannel()->adminsCount > 1) { + --peer->asChannel()->adminsCount; + } + } peer->asChannel()->mgInfo->bots.remove(user); if (peer->asChannel()->mgInfo->bots.isEmpty() && peer->asChannel()->mgInfo->botStatus > 0) { peer->asChannel()->mgInfo->botStatus = -1; @@ -1505,7 +1389,7 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo } else if (peer->isChannel()) { peer->asChannel()->setPhoto(MTP_chatPhoto(*smallLoc, *bigLoc), photo ? photo->id : 0); } - peer->photo->load(); + peer->loadUserpic(); } } } @@ -1518,15 +1402,22 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo } break; case mtpc_messageActionChatMigrateTo: { - peer->asChat()->flags |= MTPDchat::flag_deactivated; + peer->asChat()->flags |= MTPDchat::Flag::f_deactivated; //const MTPDmessageActionChatMigrateTo &d(action.c_messageActionChatMigrateTo()); - //PeerData *channel = App::peerLoaded(peerFromChannel(d.vchannel_id)); + //PeerData *channel = App::channelLoaded(d.vchannel_id.v); } break; case mtpc_messageActionChannelMigrateFrom: { //const MTPDmessageActionChannelMigrateFrom &d(action.c_messageActionChannelMigrateFrom()); - //PeerData *chat = App::peerLoaded(peerFromChat(d.vchat_id)); + //PeerData *chat = App::chatLoaded(d.vchat_id.v); + } break; + + case mtpc_messageActionPinMessage: { + if (d.has_reply_to_msg_id() && result && result->history()->peer->isMegagroup()) { + result->history()->peer->asChannel()->mgInfo->pinnedMsgId = d.vreply_to_msg_id.v; + if (App::main()) emit App::main()->peerUpdated(result->history()->peer); + } } break; } } @@ -1537,48 +1428,23 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo App::checkSavedGif(result); } - return regItem(result); + return result; } -HistoryItem *History::createItemForwarded(HistoryBlock *block, MsgId id, QDateTime date, int32 from, HistoryMessage *msg) { - return regItem(new HistoryForwarded(this, block, id, date, from, msg)); +HistoryItem *History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *msg) { + return HistoryMessage::create(this, id, flags, date, from, msg); } -HistoryItem *History::createItemDocument(HistoryBlock *block, MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption) { - HistoryItem *result = 0; - - if ((flags & MTPDmessage::flag_reply_to_msg_id) && replyTo > 0) { - result = new HistoryReply(this, block, id, flags, viaBotId, replyTo, date, from, doc, caption); - } else { - result = new HistoryMessage(this, block, id, flags, viaBotId, date, from, doc, caption); - } - - return regItem(result); +HistoryItem *History::createItemDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) { + return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, doc, caption, markup); } -HistoryItem *History::createItemPhoto(HistoryBlock *block, MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption) { - HistoryItem *result = 0; - - if (flags & MTPDmessage::flag_reply_to_msg_id && replyTo > 0) { - result = new HistoryReply(this, block, id, flags, viaBotId, replyTo, date, from, photo, caption); - } else { - result = new HistoryMessage(this, block, id, flags, viaBotId, date, from, photo, caption); - } - - return regItem(result); +HistoryItem *History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) { + return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, photo, caption, markup); } -HistoryItem *History::addNewService(MsgId msgId, QDateTime date, const QString &text, int32 flags, HistoryMedia *media, bool newMsg) { - HistoryBlock *to = 0; - bool newBlock = blocks.isEmpty(); - if (newBlock) { - to = new HistoryBlock(this); - } else { - to = blocks.back(); - } - - HistoryItem *result = new HistoryServiceMsg(this, to, msgId, date, text, flags, media); - return addNewItem(to, newBlock, regItem(result), newMsg); +HistoryItem *History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) { + return addNewItem(HistoryService::create(this, msgId, date, text, flags), newMsg); } HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type) { @@ -1596,75 +1462,32 @@ HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type) return item; } - HistoryBlock *to = 0; - bool newBlock = blocks.isEmpty(); - if (newBlock) { - to = new HistoryBlock(this); - } else { - to = blocks.back(); + return addNewToLastBlock(msg, type); +} + +HistoryItem *History::addNewToLastBlock(const MTPMessage &msg, NewMessageType type) { + bool applyServiceAction = (type == NewMessageUnread), detachExistingItem = (type != NewMessageLast); + HistoryItem *item = createItem(msg, applyServiceAction, detachExistingItem); + if (!item || !item->detached()) { + return item; } - HistoryItem *item = createItem((type == NewMessageLast) ? 0 : to, msg, (type == NewMessageUnread)); - if (type == NewMessageLast) { - if (!item->detached()) { - return item; - } - item->attach(to); - } - return addNewItem(to, newBlock, item, (type == NewMessageUnread)); + return addNewItem(item, (type == NewMessageUnread)); } HistoryItem *History::addToHistory(const MTPMessage &msg) { - return createItem(0, msg, false); + return createItem(msg, false, false); } -HistoryItem *History::addNewForwarded(MsgId id, QDateTime date, int32 from, HistoryMessage *item) { - HistoryBlock *to = 0; - bool newBlock = blocks.isEmpty(); - if (newBlock) { - to = new HistoryBlock(this); - } else { - to = blocks.back(); - } - return addNewItem(to, newBlock, createItemForwarded(to, id, date, from, item), true); +HistoryItem *History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *item) { + return addNewItem(createItemForwarded(id, flags, date, from, item), true); } -HistoryItem *History::addNewDocument(MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption) { - HistoryBlock *to = 0; - bool newBlock = blocks.isEmpty(); - if (newBlock) { - to = new HistoryBlock(this); - } else { - to = blocks.back(); - } - return addNewItem(to, newBlock, createItemDocument(to, id, flags, viaBotId, replyTo, date, from, doc, caption), true); +HistoryItem *History::addNewDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) { + return addNewItem(createItemDocument(id, flags, viaBotId, replyTo, date, from, doc, caption, markup), true); } -HistoryItem *History::addNewPhoto(MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption) { - HistoryBlock *to = 0; - bool newBlock = blocks.isEmpty(); - if (newBlock) { - to = new HistoryBlock(this); - } else { - to = blocks.back(); - } - return addNewItem(to, newBlock, createItemPhoto(to, id, flags, viaBotId, replyTo, date, from, photo, caption), true); -} - -void History::createInitialDateBlock(const QDateTime &date) { - HistoryBlock *dateBlock = new HistoryBlock(this); // date block - HistoryItem *dayItem = createDayServiceMsg(this, dateBlock, date); - dateBlock->items.push_back(dayItem); - if (width) { - dateBlock->height += dayItem->resize(width); - } - - blocks.push_front(dateBlock); - if (width) { - height += dateBlock->height; - for (int32 i = 1, l = blocks.size(); i < l; ++i) { - blocks.at(i)->y += dateBlock->height; - } - } +HistoryItem *History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) { + return addNewItem(createItemPhoto(id, flags, viaBotId, replyTo, date, from, photo, caption, markup), true); } bool History::addToOverview(MediaOverviewType type, MsgId msgId, AddToOverviewMethod method) { @@ -1710,37 +1533,11 @@ void History::eraseFromOverview(MediaOverviewType type, MsgId msgId) { if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, type); } -HistoryItem *History::addNewItem(HistoryBlock *to, bool newBlock, HistoryItem *adding, bool newMsg) { - if (!adding) { - if (newBlock) delete to; - return adding; - } +HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) { + t_assert(!isBuildingFrontBlock()); + addItemToBlock(adding); - if (newBlock) { - createInitialDateBlock(adding->date); - - to->y = height; - blocks.push_back(to); - } else if (to->items.back()->date.date() != adding->date.date()) { - HistoryItem *dayItem = createDayServiceMsg(this, to, adding->date); - to->items.push_back(dayItem); - if (width) { - dayItem->y = to->height; - - int32 dh = dayItem->resize(width); - to->height += dh; - height += dh; - } - } - to->items.push_back(adding); setLastMessage(adding); - - adding->y = to->height; - if (width) { - int32 dh = adding->resize(width); - to->height += dh; - height += dh; - } if (newMsg) { newItemAdded(adding); } @@ -1754,7 +1551,7 @@ HistoryItem *History::addNewItem(HistoryBlock *to, bool newBlock, HistoryItem *a } else if (peer->isMegagroup()) { lastAuthors = &peer->asChannel()->mgInfo->lastParticipants; if (adding->from()->asUser()->botInfo) { - peer->asChannel()->mgInfo->bots.insert(adding->from()->asUser(), true); + peer->asChannel()->mgInfo->bots.insert(adding->from()->asUser()); if (peer->asChannel()->mgInfo->botStatus != 0 && peer->asChannel()->mgInfo->botStatus < 2) { peer->asChannel()->mgInfo->botStatus = 2; } @@ -1772,19 +1569,19 @@ HistoryItem *History::addNewItem(HistoryBlock *to, bool newBlock, HistoryItem *a } } } - if (adding->hasReplyMarkup()) { - int32 markupFlags = App::replyMarkup(channelId(), adding->id).flags; - if (!(markupFlags & MTPDreplyKeyboardMarkup::flag_selective) || adding->mentionsMe()) { - QMap *markupSenders = 0; + if (adding->definesReplyKeyboard()) { + MTPDreplyKeyboardMarkup::Flags markupFlags = adding->replyKeyboardFlags(); + if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || adding->mentionsMe()) { + OrderedSet *markupSenders = 0; if (peer->isChat()) { markupSenders = &peer->asChat()->markupSenders; } else if (peer->isMegagroup()) { markupSenders = &peer->asChannel()->mgInfo->markupSenders; } if (markupSenders) { - markupSenders->insert(adding->from(), true); + markupSenders->insert(adding->from()); } - if (markupFlags & MTPDreplyKeyboardMarkup_flag_ZERO) { // zero markup means replyKeyboardHide + if (markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_zero) { // zero markup means replyKeyboardHide if (lastKeyboardFrom == adding->from()->id || (!lastKeyboardInited && !peer->isChat() && !peer->isMegagroup() && !adding->out())) { clearLastKeyboard(); } @@ -1815,27 +1612,29 @@ void History::unregTyping(UserData *from) { uint64 updateAtMs = 0; TypingUsers::iterator i = typing.find(from); if (i != typing.end()) { - updateAtMs = getms(true); + updateAtMs = getms(); i.value() = updateAtMs; } SendActionUsers::iterator j = sendActions.find(from); if (j != sendActions.end()) { - if (!updateAtMs) updateAtMs = getms(true); + if (!updateAtMs) updateAtMs = getms(); j.value().until = updateAtMs; } if (updateAtMs) { - updateTyping(updateAtMs, 0, true); + updateTyping(updateAtMs, true); } } void History::newItemAdded(HistoryItem *item) { App::checkImageCacheSize(); if (item->from() && item->from()->isUser()) { - unregTyping(item->from()->asUser()); + if (item->from() == item->author()) { + unregTyping(item->from()->asUser()); + } item->from()->asUser()->madeAction(); } if (item->out()) { - if (unreadBar) unreadBar->destroy(); + if (unreadBar) unreadBar->destroyUnreadBar(); if (!item->unread()) { outboxRead(item); } @@ -1850,52 +1649,50 @@ void History::newItemAdded(HistoryItem *item) { } } -HistoryItem *History::addItemAfterPrevToBlock(HistoryItem *item, HistoryItem *prev, HistoryBlock *block) { - if (prev && prev->date.date() != item->date.date()) { - HistoryItem *dayItem = createDayServiceMsg(this, prev->block(), item->date); - prev->block()->items.push_back(dayItem); - if (width) { - dayItem->y = prev->block()->height; - prev->block()->height += dayItem->resize(width); - if (prev->block() != block) { - height += dayItem->height(); - } +HistoryBlock *History::prepareBlockForAddingItem() { + if (isBuildingFrontBlock()) { + if (_buildingFrontBlock->block) { + return _buildingFrontBlock->block; } + + HistoryBlock *result = _buildingFrontBlock->block = new HistoryBlock(this); + if (_buildingFrontBlock->expectedItemsCount > 0) { + result->items.reserve(_buildingFrontBlock->expectedItemsCount + 1); + } + result->setIndexInHistory(0); + blocks.push_front(result); + for (int i = 1, l = blocks.size(); i < l; ++i) { + blocks.at(i)->setIndexInHistory(i); + } + return result; } + + bool addNewBlock = blocks.isEmpty() || (blocks.back()->items.size() >= MessagesPerPage); + if (!addNewBlock) { + return blocks.back(); + } + + HistoryBlock *result = new HistoryBlock(this); + result->setIndexInHistory(blocks.size()); + blocks.push_back(result); + + result->items.reserve(MessagesPerPage); + return result; +}; + +void History::addItemToBlock(HistoryItem *item) { + t_assert(item != nullptr); + t_assert(item->detached()); + + HistoryBlock *block = prepareBlockForAddingItem(); + + item->attachToBlock(block, block->items.size()); block->items.push_back(item); - if (width) { - item->y = block->height; - block->height += item->resize(width); - } - return item; -} + item->previousItemChanged(); -HistoryItem *History::addMessageGroupAfterPrevToBlock(const MTPDmessageGroup &group, HistoryItem *prev, HistoryBlock *block) { - if (prev && prev->type() == HistoryItemGroup) { - static_cast(prev)->uniteWith(group.vmin_id.v, group.vmax_id.v, group.vcount.v); - return prev; + if (isBuildingFrontBlock() && _buildingFrontBlock->expectedItemsCount > 0) { + --_buildingFrontBlock->expectedItemsCount; } - return addItemAfterPrevToBlock(regItem(new HistoryGroup(this, block, group, prev ? prev->date : date(group.vdate))), prev, block); -} - -HistoryItem *History::addMessageGroupAfterPrev(HistoryItem *newItem, HistoryItem *prev) { - if (prev && prev->type() == HistoryItemGroup) { - static_cast(prev)->uniteWith(newItem); - return prev; - } - - QDateTime date = prev ? prev->date : newItem->date; - HistoryBlock *block = prev ? prev->block() : 0; - if (!block) { - createInitialDateBlock(date); - - block = new HistoryBlock(this); - blocks.push_back(block); // CHECK - if (width) { - block->y = height; - } - } - return addItemAfterPrevToBlock(regItem(new HistoryGroup(this, block, newItem, date)), prev, block); } void History::addOlderSlice(const QVector &slice, const QVector *collapsed) { @@ -1912,17 +1709,11 @@ void History::addOlderSlice(const QVector &slice, const QVectorconstData() : 0, *groupsIt = groupsBegin, *groupsEnd = (isChannel() && collapsed) ? (groupsBegin + collapsed->size()) : 0; - HistoryItem *oldFirst = 0, *last = 0; - if (!blocks.isEmpty()) { - t_assert(blocks.size() > 1); - oldFirst = blocks.at(1)->items.front(); - } + startBuildingFrontBlock(slice.size() + (collapsed ? collapsed->size() : 0)); - HistoryBlock *block = new HistoryBlock(this); - block->items.reserve(slice.size() + (collapsed ? collapsed->size() : 0)); - for (QVector::const_iterator i = slice.cend(), e = slice.cbegin(); i != e;) { + for (auto i = slice.cend(), e = slice.cbegin(); i != e;) { --i; - HistoryItem *adding = createItem(block, *i, false); + HistoryItem *adding = createItem(*i, false, true); if (!adding) continue; for (; groupsIt != groupsEnd; ++groupsIt) { @@ -1930,132 +1721,95 @@ void History::addOlderSlice(const QVector &slice, const QVectorc_messageGroup()); if (group.vmin_id.v >= adding->id) break; - last = addMessageGroupAfterPrevToBlock(group, last, block); + addMessageGroup(group); } - last = addItemAfterPrevToBlock(adding, last, block); + addItemToBlock(adding); } for (; groupsIt != groupsEnd; ++groupsIt) { if (groupsIt->type() != mtpc_messageGroup) continue; const MTPDmessageGroup &group(groupsIt->c_messageGroup()); - last = addMessageGroupAfterPrevToBlock(group, last, block); + addMessageGroup(group); } - while (oldFirst && last && oldFirst->type() == HistoryItemGroup && last->type() == HistoryItemGroup) { - static_cast(last)->uniteWith(static_cast(oldFirst)); - oldFirst->destroy(); - if (blocks.isEmpty()) { - oldFirst = 0; - } else { - t_assert(blocks.size() > 1); - oldFirst = blocks.at(1)->items.front(); - } - } - if (oldFirst && last && last->date.date() != oldFirst->date.date()) { - HistoryItem *dayItem = createDayServiceMsg(this, block, oldFirst->date); - block->items.push_back(dayItem); - if (width) { - dayItem->y = block->height; - block->height += dayItem->resize(width); - } - } - if (block->items.isEmpty()) { + HistoryBlock *block = finishBuildingFrontBlock(); + if (!block) { + // If no items were added it means we've loaded everything old. oldLoaded = true; - delete block; - } else { - if (oldFirst) { - HistoryBlock *initial = blocks.at(0); - blocks[0] = block; - blocks.push_front(initial); - if (width) { - block->y = initial->height; - for (int32 i = 2, l = blocks.size(); i < l; ++i) { - blocks.at(i)->y += block->height; - } - height += block->height; - } - initial->items.at(0)->setDate(block->items.at(0)->date); - } else { - blocks.push_front(block); - if (width) { - height = block->height; - } - createInitialDateBlock(block->items.at(0)->date); + } else if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors / lastParticipants + bool channel = isChannel(); + int32 mask = 0; + QList *lastAuthors = 0; + OrderedSet *markupSenders = 0; + if (peer->isChat()) { + lastAuthors = &peer->asChat()->lastAuthors; + markupSenders = &peer->asChat()->markupSenders; + } else if (peer->isMegagroup()) { + lastAuthors = &peer->asChannel()->mgInfo->lastParticipants; + markupSenders = &peer->asChannel()->mgInfo->markupSenders; } - - if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors / lastParticipants - bool channel = isChannel(); - int32 mask = 0; - QList *lastAuthors = 0; - QMap *markupSenders = 0; - if (peer->isChat()) { - lastAuthors = &peer->asChat()->lastAuthors; - markupSenders = &peer->asChat()->markupSenders; - } else if (peer->isMegagroup()) { - lastAuthors = &peer->asChannel()->mgInfo->lastParticipants; - markupSenders = &peer->asChannel()->mgInfo->markupSenders; - } - for (int32 i = block->items.size(); i > 0; --i) { - HistoryItem *item = block->items[i - 1]; - mask |= item->addToOverview(AddToOverviewFront); - if (item->from()->id) { - if (lastAuthors) { // chats - if (item->from()->isUser()) { - if (!lastAuthors->contains(item->from()->asUser())) { - lastAuthors->push_back(item->from()->asUser()); - if (peer->isMegagroup()) { - peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated; - } + for (int32 i = block->items.size(); i > 0; --i) { + HistoryItem *item = block->items[i - 1]; + mask |= item->addToOverview(AddToOverviewFront); + if (item->from()->id) { + if (lastAuthors) { // chats + if (item->from()->isUser()) { + if (!lastAuthors->contains(item->from()->asUser())) { + lastAuthors->push_back(item->from()->asUser()); + if (peer->isMegagroup()) { + peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated; } } } - if (markupSenders) { // chats with bots - if (!lastKeyboardInited && item->hasReplyMarkup() && !item->out()) { - int32 markupFlags = App::replyMarkup(channelId(), item->id).flags; - if (!(markupFlags & MTPDreplyKeyboardMarkup::flag_selective) || item->mentionsMe()) { - bool wasKeyboardHide = markupSenders->contains(item->from()); - if (!wasKeyboardHide) { - markupSenders->insert(item->from(), true); - } - if (!(markupFlags & MTPDreplyKeyboardMarkup_flag_ZERO)) { - if (!lastKeyboardInited) { - bool botNotInChat = false; - if (peer->isChat()) { - botNotInChat = (!peer->canWrite() || !peer->asChat()->participants.isEmpty()) && item->from()->isUser() && !peer->asChat()->participants.contains(item->from()->asUser()); - } else if (peer->isMegagroup()) { - botNotInChat = (!peer->canWrite() || peer->asChannel()->mgInfo->botStatus != 0) && item->from()->isUser() && !peer->asChannel()->mgInfo->bots.contains(item->from()->asUser()); - } - if (wasKeyboardHide || botNotInChat) { - clearLastKeyboard(); - } else { - lastKeyboardInited = true; - lastKeyboardId = item->id; - lastKeyboardFrom = item->from()->id; - lastKeyboardUsed = false; - } + } + } + if (item->author()->id) { + if (markupSenders) { // chats with bots + if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) { + MTPDreplyKeyboardMarkup::Flags markupFlags = item->replyKeyboardFlags(); + if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || item->mentionsMe()) { + bool wasKeyboardHide = markupSenders->contains(item->author()); + if (!wasKeyboardHide) { + markupSenders->insert(item->author()); + } + if (!(markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_zero)) { + if (!lastKeyboardInited) { + bool botNotInChat = false; + if (peer->isChat()) { + botNotInChat = (!peer->canWrite() || !peer->asChat()->participants.isEmpty()) && item->author()->isUser() && !peer->asChat()->participants.contains(item->author()->asUser()); + } else if (peer->isMegagroup()) { + botNotInChat = (!peer->canWrite() || peer->asChannel()->mgInfo->botStatus != 0) && item->author()->isUser() && !peer->asChannel()->mgInfo->bots.contains(item->author()->asUser()); + } + if (wasKeyboardHide || botNotInChat) { + clearLastKeyboard(); + } else { + lastKeyboardInited = true; + lastKeyboardId = item->id; + lastKeyboardFrom = item->author()->id; + lastKeyboardUsed = false; } } } } - } else if (!lastKeyboardInited && item->hasReplyMarkup() && !item->out()) { // conversations with bots - int32 markupFlags = App::replyMarkup(channelId(), item->id).flags; - if (!(markupFlags & MTPDreplyKeyboardMarkup::flag_selective) || item->mentionsMe()) { - if (markupFlags & MTPDreplyKeyboardMarkup_flag_ZERO) { - clearLastKeyboard(); - } else { - lastKeyboardInited = true; - lastKeyboardId = item->id; - lastKeyboardFrom = item->from()->id; - lastKeyboardUsed = false; - } + } + } else if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) { // conversations with bots + MTPDreplyKeyboardMarkup::Flags markupFlags = item->replyKeyboardFlags(); + if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || item->mentionsMe()) { + if (markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_zero) { + clearLastKeyboard(); + } else { + lastKeyboardInited = true; + lastKeyboardId = item->id; + lastKeyboardFrom = item->author()->id; + lastKeyboardUsed = false; } } } } - for (int32 t = 0; t < OverviewCount; ++t) { - if ((mask & (1 << t)) && App::wnd()) App::wnd()->mediaOverviewUpdated(peer, MediaOverviewType(t)); - } + } + for (int32 t = 0; t < OverviewCount; ++t) { + if ((mask & (1 << t)) && App::wnd()) App::wnd()->mediaOverviewUpdated(peer, MediaOverviewType(t)); } } @@ -2074,16 +1828,14 @@ void History::addNewerSlice(const QVector &slice, const QVectorisEmpty())) { const MTPMessageGroup *groupsBegin = (isChannel() && collapsed) ? collapsed->constData() : 0, *groupsIt = groupsBegin, *groupsEnd = (isChannel() && collapsed) ? (groupsBegin + collapsed->size()) : 0; - HistoryItem *prev = blocks.isEmpty() ? 0 : blocks.back()->items.back(); - - HistoryBlock *block = new HistoryBlock(this); - block->items.reserve(slice.size() + (collapsed ? collapsed->size() : 0)); - for (QVector::const_iterator i = slice.cend(), e = slice.cbegin(); i != e;) { + bool atLeastOneAdded = false; + for (auto i = slice.cend(), e = slice.cbegin(); i != e;) { --i; - HistoryItem *adding = createItem(block, *i, false); + HistoryItem *adding = createItem(*i, false, true); if (!adding) continue; for (; groupsIt != groupsEnd; ++groupsIt) { @@ -2091,31 +1843,22 @@ void History::addNewerSlice(const QVector &slice, const QVectorc_messageGroup()); if (group.vmin_id.v >= adding->id) break; - prev = addMessageGroupAfterPrevToBlock(group, prev, block); + addMessageGroup(group); } - prev = addItemAfterPrevToBlock(adding, prev, block); + addItemToBlock(adding); + atLeastOneAdded = true; } for (; groupsIt != groupsEnd; ++groupsIt) { if (groupsIt->type() != mtpc_messageGroup) continue; const MTPDmessageGroup &group(groupsIt->c_messageGroup()); - prev = addMessageGroupAfterPrevToBlock(group, prev, block); + addMessageGroup(group); } - if (block->items.isEmpty()) { + if (!atLeastOneAdded) { newLoaded = true; setLastMessage(lastImportantMessage()); - delete block; - } else { - blocks.push_back(block); - if (width) { - block->y = height; - height += block->height; - } - if (blocks.size() == 1) { - createInitialDateBlock(block->items.at(0)->date); - } } } @@ -2129,11 +1872,9 @@ void History::addNewerSlice(const QVector &slice, const QVectoritems.size(); ++j) { - mask |= b->items[j]->addToOverview(AddToOverviewBack); + for_const (HistoryBlock *block, blocks) { + for_const (HistoryItem *item, block->items) { + mask |= item->addToOverview(AddToOverviewBack); } } for (int32 t = 0; t < OverviewCount; ++t) { @@ -2144,11 +1885,11 @@ void History::addNewerSlice(const QVector &slice, const QVectorcheckJoinedMessage(); } -int32 History::countUnread(MsgId upTo) { - int32 result = 0; - for (Blocks::const_iterator i = blocks.cend(), e = blocks.cbegin(); i != e;) { +int History::countUnread(MsgId upTo) { + int result = 0; + for (auto i = blocks.cend(), e = blocks.cbegin(); i != e;) { --i; - for (HistoryBlock::Items::const_iterator j = (*i)->items.cend(), en = (*i)->items.cbegin(); j != en;) { + for (auto j = (*i)->items.cend(), en = (*i)->items.cbegin(); j != en;) { --j; if ((*j)->id > 0 && (*j)->id <= upTo) { break; @@ -2163,9 +1904,9 @@ int32 History::countUnread(MsgId upTo) { void History::updateShowFrom() { if (showFrom) return; - for (Blocks::const_iterator i = blocks.cend(); i != blocks.cbegin();) { + for (auto i = blocks.cend(); i != blocks.cbegin();) { --i; - for (HistoryBlock::Items::const_iterator j = (*i)->items.cend(); j != (*i)->items.cbegin();) { + for (auto j = (*i)->items.cend(); j != (*i)->items.cbegin();) { --j; if ((*j)->type() == HistoryItemMsg && (*j)->id > 0 && (!(*j)->out() || !showFrom)) { if ((*j)->id >= inboxReadBefore) { @@ -2188,16 +1929,10 @@ MsgId History::inboxRead(MsgId upTo) { if (!upTo) upTo = msgIdForRead(); inboxReadBefore = qMax(inboxReadBefore, upTo + 1); - if (App::main()) { - if (!dialogs.isEmpty()) { - App::main()->dlgUpdated(dialogs[0]); - } - if (peer->migrateTo()) { - if (History *h = App::historyLoaded(peer->migrateTo()->id)) { - if (!h->dialogs.isEmpty()) { - App::main()->dlgUpdated(h->dialogs[0]); - } - } + updateChatListEntry(); + if (peer->migrateTo()) { + if (History *h = App::historyLoaded(peer->migrateTo()->id)) { + h->updateChatListEntry(); } } @@ -2225,33 +1960,38 @@ MsgId History::outboxRead(HistoryItem *wasRead) { } HistoryItem *History::lastImportantMessage() const { - if (isEmpty()) return 0; - bool channel = isChannel(); - for (int32 blockIndex = blocks.size(); blockIndex > 0;) { + if (isEmpty()) { + return nullptr; + } + bool importantOnly = isChannel() && !isMegagroup(); + for (int blockIndex = blocks.size(); blockIndex > 0;) { HistoryBlock *block = blocks.at(--blockIndex); - for (int32 itemIndex = block->items.size(); itemIndex > 0;) { + for (int itemIndex = block->items.size(); itemIndex > 0;) { HistoryItem *item = block->items.at(--itemIndex); - if ((channel && !isMegagroup()) ? item->isImportant() : (item->type() == HistoryItemMsg)) { + if (importantOnly ? item->isImportant() : (item->type() == HistoryItemMsg)) { return item; } } } - return 0; + return nullptr; } -void History::setUnreadCount(int32 newUnreadCount, bool psUpdate) { +void History::setUnreadCount(int newUnreadCount, bool psUpdate) { if (unreadCount != newUnreadCount) { if (newUnreadCount == 1) { if (loadedAtBottom()) showFrom = lastImportantMessage(); inboxReadBefore = qMax(inboxReadBefore, msgIdForRead()); } else if (!newUnreadCount) { - showFrom = 0; + showFrom = nullptr; inboxReadBefore = qMax(inboxReadBefore, msgIdForRead() + 1); } - App::histories().unreadFull += newUnreadCount - unreadCount; - if (mute) App::histories().unreadMuted += newUnreadCount - unreadCount; + if (inChatList()) { + App::histories().unreadIncrement(newUnreadCount - unreadCount, mute); + if (psUpdate && (!mute || cIncludeMuted()) && App::wnd()) { + App::wnd()->updateCounter(); + } + } unreadCount = newUnreadCount; - if (psUpdate && (!mute || cIncludeMuted()) && App::wnd()) App::wnd()->updateCounter(); if (unreadBar) { int32 count = unreadCount; if (peer->migrateTo()) { @@ -2259,44 +1999,123 @@ void History::setUnreadCount(int32 newUnreadCount, bool psUpdate) { count += h->unreadCount; } } - unreadBar->setCount(count); + if (count > 0) { + unreadBar->setUnreadBarCount(count); + } else { + unreadBar->setUnreadBarFreezed(); + } } } } void History::setMute(bool newMute) { if (mute != newMute) { - App::histories().unreadMuted += newMute ? unreadCount : (-unreadCount); mute = newMute; - if (App::wnd()) App::wnd()->updateCounter(); - if (!dialogs.isEmpty() && App::main()) App::main()->dlgUpdated(dialogs[0]); + if (inChatList() && unreadCount) { + App::histories().unreadMuteChanged(unreadCount, newMute); + if (App::wnd()) App::wnd()->updateCounter(); + } + updateChatListEntry(); } } -void History::getNextShowFrom(HistoryBlock *block, int32 i) { +void History::getNextShowFrom(HistoryBlock *block, int i) { if (i >= 0) { - int32 l = block->items.size(); + int l = block->items.size(); for (++i; i < l; ++i) { - if (block->items[i]->type() == HistoryItemMsg) { - showFrom = block->items[i]; + if (block->items.at(i)->type() == HistoryItemMsg) { + showFrom = block->items.at(i); return; } } } - int32 j = blocks.indexOf(block), s = blocks.size(); - if (j >= 0) { - for (++j; j < s; ++j) { - block = blocks[j]; - for (int32 i = 0, l = block->items.size(); i < l; ++i) { - if (block->items[i]->type() == HistoryItemMsg) { - showFrom = block->items[i]; - return; - } + for (int j = block->indexInHistory() + 1, s = blocks.size(); j < s; ++j) { + block = blocks.at(j); + for_const (HistoryItem *item, block->items) { + if (item->type() == HistoryItemMsg) { + showFrom = item; + return; } } } - showFrom = 0; + showFrom = nullptr; +} + +void History::countScrollState(int top) { + countScrollTopItem(top); + if (scrollTopItem) { + scrollTopOffset = (top - scrollTopItem->block()->y - scrollTopItem->y); + } +} + +void History::countScrollTopItem(int top) { + if (isEmpty()) { + forgetScrollState(); + return; + } + + int itemIndex = 0, blockIndex = 0, itemTop = 0; + if (scrollTopItem && !scrollTopItem->detached()) { + itemIndex = scrollTopItem->indexInBlock(); + blockIndex = scrollTopItem->block()->indexInHistory(); + itemTop = blocks.at(blockIndex)->y + scrollTopItem->y; + } + if (itemTop > top) { + // go backward through history while we don't find an item that starts above + do { + HistoryBlock *block = blocks.at(blockIndex); + for (--itemIndex; itemIndex >= 0; --itemIndex) { + HistoryItem *item = block->items.at(itemIndex); + itemTop = block->y + item->y; + if (itemTop <= top) { + scrollTopItem = item; + return; + } + } + if (--blockIndex >= 0) { + itemIndex = blocks.at(blockIndex)->items.size(); + } else { + break; + } + } while (true); + + scrollTopItem = blocks.front()->items.front(); + } else { + // go forward through history while we don't find the last item that starts above + for (int blocksCount = blocks.size(); blockIndex < blocksCount; ++blockIndex) { + HistoryBlock *block = blocks.at(blockIndex); + for (int itemsCount = block->items.size(); itemIndex < itemsCount; ++itemIndex) { + HistoryItem *item = block->items.at(itemIndex); + itemTop = block->y + item->y; + if (itemTop > top) { + t_assert(itemIndex > 0 || blockIndex > 0); + if (itemIndex > 0) { + scrollTopItem = block->items.at(itemIndex - 1); + } else { + scrollTopItem = blocks.at(blockIndex - 1)->items.back(); + } + return; + } + } + itemIndex = 0; + } + scrollTopItem = blocks.back()->items.back(); + } +} + +void History::getNextScrollTopItem(HistoryBlock *block, int32 i) { + ++i; + if (i > 0 && i < block->items.size()) { + scrollTopItem = block->items.at(i); + return; + } + int j = block->indexInHistory() + 1; + if (j > 0 && j < blocks.size()) { + scrollTopItem = blocks.at(j)->items.front(); + return; + } + scrollTopItem = nullptr; } void History::addUnreadBar() { @@ -2308,37 +2127,103 @@ void History::addUnreadBar() { count += h->unreadCount; } } - HistoryBlock *block = showFrom->block(); - unreadBar = new HistoryUnreadBar(this, block, count, showFrom->date); - if (!addNewInTheMiddle(regItem(unreadBar), blocks.indexOf(block), block->items.indexOf(showFrom))) { - unreadBar = 0; + showFrom->setUnreadBarCount(count); + unreadBar = showFrom; +} + +void History::destroyUnreadBar() { + if (unreadBar) { + unreadBar->destroyUnreadBar(); } } HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex) { - if (blockIndex < 0 || itemIndex < 0 || blockIndex >= blocks.size() || itemIndex > blocks.at(blockIndex)->items.size()) { - delete newItem; - return 0; - } + t_assert(blockIndex >= 0); + t_assert(blockIndex < blocks.size()); + t_assert(itemIndex >= 0); + t_assert(itemIndex <= blocks.at(blockIndex)->items.size()); HistoryBlock *block = blocks.at(blockIndex); - newItem->y = (itemIndex < block->items.size()) ? block->items.at(itemIndex)->y : block->height; - block->items.insert(itemIndex, newItem); - if (width) { - int32 dh = newItem->resize(width), l = block->items.size(); - for (++itemIndex; itemIndex < l; ++itemIndex) { - block->items[itemIndex]->y += dh; - } - block->height += dh; - for (++blockIndex, l = blocks.size(); blockIndex < l; ++blockIndex) { - blocks[blockIndex]->y += dh; - } - height += dh; + newItem->attachToBlock(block, itemIndex); + block->items.insert(itemIndex, newItem); + newItem->previousItemChanged(); + for (int i = itemIndex + 1, l = block->items.size(); i < l; ++i) { + block->items.at(i)->setIndexInBlock(i); } + if (itemIndex + 1 < block->items.size()) { + block->items.at(itemIndex + 1)->previousItemChanged(); + } + return newItem; } +template +void History::addMessageGroup(CreateGroup create, UniteGroup unite) { + HistoryItem *previous = nullptr; + if (isBuildingFrontBlock()) { + if (_buildingFrontBlock->block) { + previous = _buildingFrontBlock->block->items.back(); + } + } else { + if (!blocks.isEmpty()) { + previous = blocks.back()->items.back(); + } + } + + if (previous && previous->type() == HistoryItemGroup) { + unite(static_cast(previous)); + } else { + addItemToBlock(create(previous)); + } +} + +void History::addMessageGroup(const MTPDmessageGroup &group) { + addMessageGroup([&group, this](HistoryItem *previous) -> HistoryGroup* { // create(..) + return HistoryGroup::create(this, group, previous ? previous->date : date(group.vdate)); + }, [&group](HistoryGroup *existing) { // unite(..) + existing->uniteWith(group.vmin_id.v, group.vmax_id.v, group.vcount.v); + }); +} + +void History::startBuildingFrontBlock(int expectedItemsCount) { + t_assert(!isBuildingFrontBlock()); + t_assert(expectedItemsCount > 0); + + _buildingFrontBlock.reset(new BuildingBlock()); + _buildingFrontBlock->expectedItemsCount = expectedItemsCount; +} + +HistoryBlock *History::finishBuildingFrontBlock() { + t_assert(isBuildingFrontBlock()); + + // Some checks if there was some message history already + HistoryBlock *block = _buildingFrontBlock->block; + if (block && blocks.size() > 1) { + HistoryItem *last = block->items.back(); // ... item, item, item, last ], [ first, item, item ... + HistoryItem *first = blocks.at(1)->items.front(); + + // we've added a new front block, so previous item for + // the old first item of a first block was changed + first->previousItemChanged(); + + // we've added a new front block, now we check if both + // last message of the first block and first message of + // the second block are groups, if they are - unite them + if (first->type() == HistoryItemGroup && last->type() == HistoryItemGroup) { + static_cast(first)->uniteWith(static_cast(last)); + last->destroy(); + + // last->destroy() could've destroyed this new block + // so we can't rely on this pointer any more + block = _buildingFrontBlock->block; + } + } + + _buildingFrontBlock.clear(); + return block; +} + void History::clearNotifications() { notifies.clear(); } @@ -2390,8 +2275,6 @@ void History::getReadyFor(MsgId msgId, MsgId &fixInScrollMsgId, int32 &fixInScro h->getReadyFor(-msgId, fixInScrollMsgId, fixInScrollMsgTop); if (h->isEmpty()) { clear(true); - newLoaded = oldLoaded = false; - lastWidth = 0; } return; } @@ -2402,8 +2285,6 @@ void History::getReadyFor(MsgId msgId, MsgId &fixInScrollMsgId, int32 &fixInScro if (History *h = App::historyLoaded(peer->migrateFrom()->id)) { if (h->unreadCount) { clear(true); - newLoaded = oldLoaded = false; - lastWidth = 0; h->getReadyFor(msgId, fixInScrollMsgId, fixInScrollMsgTop); return; } @@ -2411,9 +2292,9 @@ void History::getReadyFor(MsgId msgId, MsgId &fixInScrollMsgId, int32 &fixInScro } if (!isReadyFor(msgId, fixInScrollMsgId, fixInScrollMsgTop)) { clear(true); - newLoaded = (msgId == ShowAtTheEndMsgId); - oldLoaded = false; - lastWidth = 0; + if (msgId == ShowAtTheEndMsgId) { + newLoaded = true; + } } } @@ -2433,25 +2314,25 @@ void History::setLastMessage(HistoryItem *msg) { if (msg) { if (!lastMsg) Local::removeSavedPeer(peer); lastMsg = msg; - setPosInDialogsDate(msg->date); + setChatsListDate(msg->date); } else { lastMsg = 0; } - if (!dialogs.isEmpty() && App::main()) App::main()->dlgUpdated(dialogs[0]); + updateChatListEntry(); } -void History::setPosInDialogsDate(const QDateTime &date) { - bool updateDialog = (App::main() && (!peer->isChannel() || peer->asChannel()->amIn() || !dialogs.isEmpty())); - if (peer->migrateTo() && dialogs.isEmpty()) { +void History::setChatsListDate(const QDateTime &date) { + bool updateDialog = (App::main() && (!peer->isChannel() || peer->asChannel()->amIn() || !_chatListLinks.isEmpty())); + if (peer->migrateTo() && _chatListLinks.isEmpty()) { updateDialog = false; } if (!lastMsgDate.isNull() && lastMsgDate >= date) { - if (!updateDialog || !dialogs.isEmpty()) { + if (!updateDialog || !_chatListLinks.isEmpty()) { return; } } lastMsgDate = date; - posInDialogs = dialogPosFromDate(lastMsgDate); + _sortKeyInChatList = dialogPosFromDate(lastMsgDate); if (updateDialog) { App::main()->createDialog(this); } @@ -2462,10 +2343,10 @@ void History::fixLastMessage(bool wasAtBottom) { } MsgId History::minMsgId() const { - for (Blocks::const_iterator i = blocks.cbegin(), e = blocks.cend(); i != e; ++i) { - for (HistoryBlock::Items::const_iterator j = (*i)->items.cbegin(), en = (*i)->items.cend(); j != en; ++j) { - if ((*j)->id > 0) { - return (*j)->id; + for_const (const HistoryBlock *block, blocks) { + for_const (const HistoryItem *item, block->items) { + if (item->id > 0) { + return item->id; } } } @@ -2473,9 +2354,9 @@ MsgId History::minMsgId() const { } MsgId History::maxMsgId() const { - for (Blocks::const_iterator i = blocks.cend(), e = blocks.cbegin(); i != e;) { + for (auto i = blocks.cend(), e = blocks.cbegin(); i != e;) { --i; - for (HistoryBlock::Items::const_iterator j = (*i)->items.cend(), en = (*i)->items.cbegin(); j != en;) { + for (auto j = (*i)->items.cend(), en = (*i)->items.cbegin(); j != en;) { --j; if ((*j)->id > 0) { return (*j)->id; @@ -2491,26 +2372,21 @@ MsgId History::msgIdForRead() const { return result; } -int32 History::geomResize(int32 newWidth, int32 *ytransform, const HistoryItem *resizedItem) { - if (width != newWidth) resizedItem = 0; // recount all items - if (width != newWidth || resizedItem) { - int32 y = 0; - for (Blocks::iterator i = blocks.begin(), e = blocks.end(); i != e; ++i) { - HistoryBlock *block = *i; - bool updTransform = ytransform && (*ytransform >= block->y) && (*ytransform < block->y + block->height); - if (updTransform) *ytransform -= block->y; - if (block->y != y) { - block->y = y; - } - y += block->geomResize(newWidth, ytransform, resizedItem); - if (updTransform) { - *ytransform += block->y; - ytransform = 0; - } - } - width = newWidth; - height = y; +int History::resizeGetHeight(int newWidth) { + bool resizeAllItems = (_flags | Flag::f_pending_resize) || (width != newWidth); + + if (!resizeAllItems && !hasPendingResizedItems()) { + return height; } + _flags &= ~(Flag::f_pending_resize | Flag::f_has_pending_resized_items); + + width = newWidth; + int y = 0; + for_const (HistoryBlock *block, blocks) { + block->y = y; + y += block->resizeGetHeight(newWidth, resizeAllItems); + } + height = y; return height; } @@ -2524,13 +2400,16 @@ const ChannelHistory *History::asChannelHistory() const { void History::clear(bool leaveItems) { if (unreadBar) { - unreadBar->destroy(); + unreadBar = nullptr; } if (showFrom) { - showFrom = 0; + showFrom = nullptr; + } + if (scrollTopItem) { + forgetScrollState(); } if (!leaveItems) { - setLastMessage(0); + setLastMessage(nullptr); } for (int32 i = 0; i < OverviewCount; ++i) { if (!overview[i].isEmpty() || !overviewIds[i].isEmpty()) { @@ -2543,23 +2422,20 @@ void History::clear(bool leaveItems) { } overview[i].clear(); overviewIds[i].clear(); - if (App::wnd() && !App::quiting()) App::wnd()->mediaOverviewUpdated(peer, MediaOverviewType(i)); + if (App::wnd() && !App::quitting()) App::wnd()->mediaOverviewUpdated(peer, MediaOverviewType(i)); } } - for (Blocks::const_iterator i = blocks.cbegin(), e = blocks.cend(); i != e; ++i) { - if (leaveItems) { - (*i)->clear(true); - } - delete *i; - } - blocks.clear(); + clearBlocks(leaveItems); if (leaveItems) { lastKeyboardInited = false; } else { setUnreadCount(0); } - height = 0; - oldLoaded = false; + setPendingResize(); + + newLoaded = oldLoaded = false; + forgetScrollState(); + if (peer->isChat()) { peer->asChat()->lastAuthors.clear(); peer->asChat()->markupSenders.clear(); @@ -2572,6 +2448,73 @@ void History::clear(bool leaveItems) { if (leaveItems && App::main()) App::main()->historyCleared(this); } +void History::clearBlocks(bool leaveItems) { + Blocks lst; + std::swap(lst, blocks); + for_const (HistoryBlock *block, lst) { + if (leaveItems) { + block->clear(true); + } + delete block; + } +} + +void History::clearOnDestroy() { + clearBlocks(false); +} + +QPair History::adjustByPosInChatsList(DialogsIndexed &indexed) { + DialogRow *lnk = mainChatListLink(); + int32 movedFrom = lnk->pos * st::dlgHeight; + indexed.adjustByPos(_chatListLinks); + int32 movedTo = lnk->pos * st::dlgHeight; + return qMakePair(movedFrom, movedTo); +} + +DialogRow *History::addToChatList(DialogsIndexed &indexed) { + if (!inChatList()) { + _chatListLinks = indexed.addToEnd(this); + if (unreadCount) { + App::histories().unreadIncrement(unreadCount, mute); + if (App::wnd()) App::wnd()->updateCounter(); + } + } + return mainChatListLink(); +} + +void History::removeFromChatList(DialogsIndexed &indexed) { + if (inChatList()) { + indexed.del(peer); + _chatListLinks.clear(); + if (unreadCount) { + App::histories().unreadIncrement(-unreadCount, mute); + if (App::wnd()) App::wnd()->updateCounter(); + } + } +} + +void History::removeChatListEntryByLetter(QChar letter) { + t_assert(letter != 0); + if (inChatList()) { + _chatListLinks.remove(letter); + } +} + +void History::addChatListEntryByLetter(QChar letter, DialogRow *row) { + t_assert(letter != 0); + if (inChatList()) { + _chatListLinks.insert(letter, row); + } +} + +void History::updateChatListEntry() const { + if (MainWidget *m = App::main()) { + if (inChatList()) { + m->dlgUpdated(mainChatListLink()); + } + } +} + void History::overviewSliceDone(int32 overviewIndex, const MTPmessages_Messages &result, bool onlyCounts) { const QVector *v = 0; switch (result.type()) { @@ -2596,10 +2539,10 @@ void History::overviewSliceDone(int32 overviewIndex, const MTPmessages_Messages if (peer->isChannel()) { peer->asChannel()->ptsReceived(d.vpts.v); } else { - LOG(("API Error: received messages.channelMessages when no channel was passed! (History::overviewSliceDone, onlyCounts %1)").arg(logBool(onlyCounts))); + LOG(("API Error: received messages.channelMessages when no channel was passed! (History::overviewSliceDone, onlyCounts %1)").arg(Logs::b(onlyCounts))); } if (d.has_collapsed()) { // should not be returned - LOG(("API Error: channels.getMessages and messages.getMessages should not return collapsed groups! (History::overviewSliceDone, onlyCounts %1)").arg(logBool(onlyCounts))); + LOG(("API Error: channels.getMessages and messages.getMessages should not return collapsed groups! (History::overviewSliceDone, onlyCounts %1)").arg(Logs::b(onlyCounts))); } App::feedUsers(d.vusers); @@ -2652,92 +2595,80 @@ void History::changeMsgId(MsgId oldId, MsgId newId) { } } -void History::blockResized(HistoryBlock *block, int32 dh) { - int32 i = blocks.indexOf(block), l = blocks.size(); - if (i >= 0) { - for (++i; i < l; ++i) { - blocks[i]->y -= dh; - } - height -= dh; - } -} - -void History::clearUpto(MsgId msgId) { - for (HistoryItem *item = isEmpty() ? 0 : blocks.back()->items.back(); item && (item->id < 0 || item->id >= msgId); item = isEmpty() ? 0 : blocks.back()->items.back()) { - item->destroy(); - } -} - void History::removeBlock(HistoryBlock *block) { - int32 i = blocks.indexOf(block), h = block->height; - if (i >= 0) { - blocks.removeAt(i); - int32 l = blocks.size(); - if (i > 0 && l == 1) { // only fake block with date left - removeBlock(blocks[0]); - height = 0; - } else if (h) { - for (; i < l; ++i) { - blocks[i]->y -= h; - } - height -= h; - } + t_assert(block->items.isEmpty()); + + if (_buildingFrontBlock && block == _buildingFrontBlock->block) { + _buildingFrontBlock->block = nullptr; + } + + int index = block->indexInHistory(); + blocks.removeAt(index); + for (int i = index, l = blocks.size(); i < l; ++i) { + blocks.at(i)->setIndexInHistory(i); + } + if (index < blocks.size()) { + blocks.at(index)->items.front()->previousItemChanged(); } - delete block; } -int32 HistoryBlock::geomResize(int32 newWidth, int32 *ytransform, const HistoryItem *resizedItem) { - int32 y = 0; - for (Items::iterator i = items.begin(), e = items.end(); i != e; ++i) { - HistoryItem *item = *i; - bool updTransform = ytransform && (*ytransform >= item->y) && (*ytransform < item->y + item->height()); - if (updTransform) *ytransform -= item->y; +History::~History() { + clearOnDestroy(); + deleteAndMark(msgDraft); + deleteAndMark(editDraft); +} + +int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) { + int y = 0; + for_const (HistoryItem *item, items) { item->y = y; - if (!resizedItem || resizedItem == item) { - y += item->resize(newWidth); + if (resizeAllItems || item->pendingResize()) { + y += item->resizeGetHeight(newWidth); } else { y += item->height(); } - if (updTransform) { - *ytransform += item->y; - ytransform = 0; - } } height = y; return height; } void HistoryBlock::clear(bool leaveItems) { + Items lst; + std::swap(lst, items); + if (leaveItems) { - for (Items::const_iterator i = items.cbegin(), e = items.cend(); i != e; ++i) { - (*i)->detachFast(); + for_const (HistoryItem *item, lst) { + item->detachFast(); } } else { - for (Items::const_iterator i = items.cbegin(), e = items.cend(); i != e; ++i) { - delete *i; + for_const (HistoryItem *item, lst) { + delete item; } } - items.clear(); } void HistoryBlock::removeItem(HistoryItem *item) { - int32 i = items.indexOf(item), dh = 0; + t_assert(item->block() == this); + + int itemIndex = item->indexInBlock(); if (history->showFrom == item) { - history->getNextShowFrom(this, i); + history->getNextShowFrom(this, itemIndex); } - if (i < 0) { - return; + if (history->unreadBar == item) { + history->unreadBar = nullptr; + } + if (history->scrollTopItem == item) { + history->getNextScrollTopItem(this, itemIndex); } - bool createInitialDate = false; - QDateTime initialDateTime; - int32 myIndex = history->blocks.indexOf(this); - if (myIndex >= 0 && item->type() != HistoryItemDate) { // fix message groups and date items + int blockIndex = indexInHistory(); + if (blockIndex >= 0) { // fix message groups if (item->isImportant()) { // unite message groups around this important message - HistoryGroup *nextGroup = 0, *prevGroup = 0; - HistoryCollapse *nextCollapse = 0; - HistoryItem *prevItem = 0; - for (int32 nextBlock = myIndex, nextIndex = qMin(items.size(), i + 1); nextBlock < history->blocks.size(); ++nextBlock) { + HistoryGroup *nextGroup = nullptr; + HistoryGroup *prevGroup = nullptr; + HistoryCollapse *nextCollapse = nullptr; + HistoryItem *prevItem = nullptr; + for (int nextBlock = blockIndex, nextIndex = qMin(items.size(), itemIndex + 1); nextBlock < history->blocks.size(); ++nextBlock) { HistoryBlock *block = history->blocks.at(nextBlock); for (; nextIndex < block->items.size(); ++nextIndex) { HistoryItem *item = block->items.at(nextIndex); @@ -2757,7 +2688,7 @@ void HistoryBlock::removeItem(HistoryItem *item) { break; } } - for (int32 prevBlock = myIndex + 1, prevIndex = qMax(1, i); prevBlock > 0;) { + for (int prevBlock = blockIndex + 1, prevIndex = qMax(1, itemIndex); prevBlock > 0;) { --prevBlock; HistoryBlock *block = history->blocks.at(prevBlock); if (!prevIndex) prevIndex = block->items.size(); @@ -2785,89 +2716,484 @@ void HistoryBlock::removeItem(HistoryItem *item) { nextCollapse->destroy(); } } + } - // fix date items - HistoryItem *nextItem = (i < items.size() - 1) ? items[i + 1] : ((myIndex < history->blocks.size() - 1) ? history->blocks[myIndex + 1]->items[0] : 0); - if (nextItem && nextItem == history->unreadBar) { // skip unread bar - if (i < items.size() - 2) { - nextItem = items[i + 2]; - } else if (i < items.size() - 1) { - nextItem = ((myIndex < history->blocks.size() - 1) ? history->blocks[myIndex + 1]->items[0] : 0); - } else if (myIndex < history->blocks.size() - 1) { - if (0 < history->blocks[myIndex + 1]->items.size() - 1) { - nextItem = history->blocks[myIndex + 1]->items[1]; - } else if (myIndex < history->blocks.size() - 2) { - nextItem = history->blocks[myIndex + 2]->items[0]; - } else { - nextItem = 0; - } - } else { - nextItem = 0; - } - } - if (!nextItem || nextItem->type() == HistoryItemDate) { // only if there is no next item or it is a date item - HistoryItem *prevItem = (i > 0) ? items[i - 1] : 0; - if (prevItem && prevItem == history->unreadBar) { // skip unread bar - prevItem = (i > 1) ? items[i - 2] : 0; - } - if (prevItem) { - if (prevItem->type() == HistoryItemDate) { - prevItem->destroy(); - --i; - } - } else if (myIndex > 0) { - HistoryBlock *prevBlock = history->blocks[myIndex - 1]; - if (prevBlock->items.isEmpty() || ((myIndex == 1) && (prevBlock->items.size() != 1 || prevBlock->items.front()->type() != HistoryItemDate))) { - LOG(("App Error: Found bad history, with no first date block: %1").arg(history->blocks[0]->items.size())); - } else if (prevBlock->items[prevBlock->items.size() - 1]->type() == HistoryItemDate) { - prevBlock->items[prevBlock->items.size() - 1]->destroy(); - if (nextItem && myIndex == 1) { // destroy next date (for creating initial then) - initialDateTime = nextItem->date; - createInitialDate = true; - nextItem->destroy(); - } - } - } - } - } - // myIndex can be invalid now, because of destroying previous blocks + // itemIndex/blockIndex can be invalid now, because of destroying previous items/blocks + blockIndex = indexInHistory(); + itemIndex = item->indexInBlock(); - dh = item->height(); - items.remove(i); - int32 l = items.size(); - if ((!item->out() || item->fromChannel()) && item->unread() && history->unreadCount) { - history->setUnreadCount(history->unreadCount - 1); + item->detachFast(); + items.remove(itemIndex); + for (int i = itemIndex, l = items.size(); i < l; ++i) { + items.at(i)->setIndexInBlock(i); } - int32 itemType = item->type(); - if (itemType == HistoryItemUnreadBar) { - if (history->unreadBar == item) { - history->unreadBar = 0; - } - } - if (createInitialDate) { - history->createInitialDateBlock(initialDateTime); - } - History *h = history; - if (l) { - for (; i < l; ++i) { - items[i]->y -= dh; - } - height -= dh; - history->blockResized(this, dh); - } else { + if (items.isEmpty()) { history->removeBlock(this); + } else if (itemIndex < items.size()) { + items.at(itemIndex)->previousItemChanged(); + } else if (blockIndex + 1 < history->blocks.size()) { + history->blocks.at(blockIndex + 1)->items.front()->previousItemChanged(); + } + + if (items.isEmpty()) { + delete this; } } -HistoryItem::HistoryItem(History *history, HistoryBlock *block, MsgId msgId, int32 flags, QDateTime msgDate, int32 from) : y(0) +class ReplyMarkupClickHandler : public LeftButtonClickHandler { +public: + ReplyMarkupClickHandler(const HistoryItem *item, int row, int col) : _item(item), _row(row), _col(col) { + } + + QString tooltip() const override { + return _fullDisplayed ? QString() : buttonText(); + } + + void setFullDisplayed(bool full) { + _fullDisplayed = full; + } + + // Finds the corresponding button in the items markup struct. + // If the button is not found it returns nullptr. + // Note: it is possible that we will point to the different button + // than the one was used when constructing the handler, but not a big deal. + const HistoryMessageReplyMarkup::Button *getButton() const { + if (auto *markup = _item->Get()) { + if (_row < markup->rows.size()) { + const HistoryMessageReplyMarkup::ButtonRow &row(markup->rows.at(_row)); + if (_col < row.size()) { + return &row.at(_col); + } + } + } + return nullptr; + } + +protected: + void onClickImpl() const override { + App::activateBotCommand(_item, _row, _col); + } + +private: + const HistoryItem *_item = nullptr; + int _row, _col; + bool _fullDisplayed = true; + + // Returns the full text of the corresponding button. + QString buttonText() const { + if (auto button = getButton()) { + return button->text; + } + return QString(); + } + +}; + +ReplyKeyboard::ReplyKeyboard(const HistoryItem *item, StylePtr &&s) +: _item(item) +, _a_selected(animation(this, &ReplyKeyboard::step_selected)) +, _st(std_::forward(s)) { + if (auto *markup = item->Get()) { + _rows.reserve(markup->rows.size()); + for (int i = 0, l = markup->rows.size(); i != l; ++i) { + const HistoryMessageReplyMarkup::ButtonRow &row(markup->rows.at(i)); + int s = row.size(); + ButtonRow newRow(s, Button()); + for (int j = 0; j != s; ++j) { + Button &button(newRow[j]); + QString str = row.at(j).text; + button.type = row.at(j).type; + button.link.reset(new ReplyMarkupClickHandler(item, i, j)); + button.text.setText(_st->textFont(), textOneLine(str), _textPlainOptions); + button.characters = str.isEmpty() ? 1 : str.size(); + } + _rows.push_back(newRow); + } + } +} + +void ReplyKeyboard::resize(int width, int height) { + _width = width; + + auto *markup = _item->Get(); + float64 y = 0, buttonHeight = _rows.isEmpty() ? _st->buttonHeight() : (float64(height + _st->buttonSkip()) / _rows.size()); + for (ButtonRow &row : _rows) { + int s = row.size(); + + float64 widthForText = _width - ((s - 1) * _st->buttonSkip() + s * 2 * _st->buttonPadding()), widthOfText = 0.; + for_const (const Button &button, row) { + widthOfText += qMax(button.text.maxWidth(), 1); + } + + float64 x = 0, coef = widthForText / widthOfText; + for (Button &button : row) { + float64 tw = widthForText / float64(s), w = 2 * _st->buttonPadding() + tw; + float64 minw = _st->minButtonWidth(button.type); + if (w < minw) w = minw; + + int rectx = static_cast(std::floor(x)); + int rectw = static_cast(std::floor(x + w)) - rectx; + button.rect = QRect(rectx, qRound(y), rectw, qRound(buttonHeight - _st->buttonSkip())); + if (rtl()) button.rect.setX(_width - button.rect.x() - button.rect.width()); + x += w + _st->buttonSkip(); + + button.link->setFullDisplayed(tw >= button.text.maxWidth()); + } + y += buttonHeight; + } +} + +bool ReplyKeyboard::isEnoughSpace(int width, const style::botKeyboardButton &st) const { + for_const (const ButtonRow &row, _rows) { + int s = row.size(); + int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding); + for_const (const Button &button, row) { + widthLeft -= qMax(button.text.maxWidth(), 1); + if (widthLeft < 0) { + if (row.size() > 3) { + return false; + } else { + break; + } + } + } + } + return true; +} + +void ReplyKeyboard::setStyle(StylePtr &&st) { + _st = std_::move(st); +} + +int ReplyKeyboard::naturalWidth() const { + int result = 0; + + auto *markup = _item->Get(); + for_const (const ButtonRow &row, _rows) { + int rowSize = row.size(); + int rowWidth = (rowSize - 1) * _st->buttonSkip() + rowSize * 2 * _st->buttonPadding(); + for_const(const Button &button, row) { + rowWidth += qMax(button.text.maxWidth(), 1); + } + if (rowWidth > result) { + result = rowWidth; + } + } + return result; +} + +int ReplyKeyboard::naturalHeight() const { + return (_rows.size() - 1) * _st->buttonSkip() + _rows.size() * _st->buttonHeight(); +} + +void ReplyKeyboard::paint(Painter &p, const QRect &clip) const { + t_assert(!_st.isNull()); + t_assert(_width > 0); + + _st->startPaint(p); + for_const (const ButtonRow &row, _rows) { + for_const (const Button &button, row) { + QRect rect(button.rect); + if (rect.y() >= clip.y() + clip.height()) return; + if (rect.y() + rect.height() < clip.y()) continue; + + // just ignore the buttons that didn't layout well + if (rect.x() + rect.width() > _width) break; + + _st->paintButton(p, button); + } + } +} + +void ReplyKeyboard::getState(ClickHandlerPtr &lnk, int x, int y) const { + t_assert(_width > 0); + + lnk.clear(); + for_const (const ButtonRow &row, _rows) { + for_const (const Button &button, row) { + QRect rect(button.rect); + + // just ignore the buttons that didn't layout well + if (rect.x() + rect.width() > _width) break; + + if (rect.contains(x, y)) { + lnk = button.link; + return; + } + } + } +} + +void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + if (!p) return; + + bool startAnimation = false; + for (int i = 0, rows = _rows.size(); i != rows; ++i) { + const ButtonRow &row(_rows.at(i)); + for (int j = 0, cols = row.size(); j != cols; ++j) { + if (row.at(j).link == p) { + bool startAnimation = _animations.isEmpty(); + + int indexForAnimation = i * MatrixRowShift + j + 1; + if (!active) { + indexForAnimation = -indexForAnimation; + } + + _animations.remove(-indexForAnimation); + if (!_animations.contains(indexForAnimation)) { + _animations.insert(indexForAnimation, getms()); + } + + if (startAnimation && !_a_selected.animating()) { + _a_selected.start(); + } + return; + } + } + } +} + +void ReplyKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { + _st->repaint(_item); +} + +void ReplyKeyboard::step_selected(uint64 ms, bool timer) { + for (Animations::iterator i = _animations.begin(); i != _animations.end();) { + int index = qAbs(i.key()) - 1, row = (index / MatrixRowShift), col = index % MatrixRowShift; + float64 dt = float64(ms - i.value()) / st::botKbDuration; + if (dt >= 1) { + _rows[row][col].howMuchOver = (i.key() > 0) ? 1 : 0; + i = _animations.erase(i); + } else { + _rows[row][col].howMuchOver = (i.key() > 0) ? dt : (1 - dt); + ++i; + } + } + if (timer) _st->repaint(_item); + if (_animations.isEmpty()) { + _a_selected.stop(); + } +} + +void ReplyKeyboard::clearSelection() { + for (auto i = _animations.cbegin(), e = _animations.cend(); i != e; ++i) { + int index = qAbs(i.key()) - 1, row = (index / MatrixRowShift), col = index % MatrixRowShift; + _rows[row][col].howMuchOver = 0; + } + _animations.clear(); + _a_selected.stop(); +} + +void ReplyKeyboard::Style::paintButton(Painter &p, const ReplyKeyboard::Button &button) const { + const QRect &rect = button.rect; + bool pressed = ClickHandler::showAsPressed(button.link); + + paintButtonBg(p, rect, pressed, button.howMuchOver); + paintButtonIcon(p, rect, button.type); + if (button.type == HistoryMessageReplyMarkup::Button::Callback) { + if (const HistoryMessageReplyMarkup::Button *data = button.link->getButton()) { + if (data->requestId) { + paintButtonLoading(p, rect); + } + } + } + + int tx = rect.x(), tw = rect.width(); + if (tw > st::botKbFont->elidew + _st->padding * 2) { + tx += _st->padding; + tw -= _st->padding * 2; + } else if (tw > st::botKbFont->elidew) { + tx += (tw - st::botKbFont->elidew) / 2; + tw = st::botKbFont->elidew; + } + int textTop = rect.y() + (pressed ? _st->downTextTop : _st->textTop); + button.text.drawElided(p, tx, textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top); +} + +void HistoryMessageReplyMarkup::createFromButtonRows(const QVector &v) { + if (v.isEmpty()) { + rows.clear(); + return; + } + + rows.reserve(v.size()); + for_const(const MTPKeyboardButtonRow &row, v) { + switch (row.type()) { + case mtpc_keyboardButtonRow: { + const MTPDkeyboardButtonRow &r(row.c_keyboardButtonRow()); + const QVector &b(r.vbuttons.c_vector().v); + if (!b.isEmpty()) { + ButtonRow buttonRow; + buttonRow.reserve(b.size()); + for_const(const MTPKeyboardButton &button, b) { + switch (button.type()) { + case mtpc_keyboardButton: { + buttonRow.push_back({ Button::Default, qs(button.c_keyboardButton().vtext), QByteArray(), 0 }); + } break; + case mtpc_keyboardButtonCallback: { + const auto &buttonData(button.c_keyboardButtonCallback()); + buttonRow.push_back({ Button::Callback, qs(buttonData.vtext), qba(buttonData.vdata), 0 }); + } break; + case mtpc_keyboardButtonRequestGeoLocation: { + buttonRow.push_back({ Button::RequestLocation, qs(button.c_keyboardButtonRequestGeoLocation().vtext), QByteArray(), 0 }); + } break; + case mtpc_keyboardButtonRequestPhone: { + buttonRow.push_back({ Button::RequestPhone, qs(button.c_keyboardButtonRequestPhone().vtext), QByteArray(), 0 }); + } break; + case mtpc_keyboardButtonUrl: { + const auto &buttonData(button.c_keyboardButtonUrl()); + buttonRow.push_back({ Button::Url, qs(buttonData.vtext), qba(buttonData.vurl), 0 }); + } break; + case mtpc_keyboardButtonSwitchInline: { + const auto &buttonData(button.c_keyboardButtonSwitchInline()); + buttonRow.push_back({ Button::SwitchInline, qs(buttonData.vtext), qba(buttonData.vquery), 0 }); + } break; + } + } + if (!buttonRow.isEmpty()) rows.push_back(buttonRow); + } + } break; + } + } +} + +void HistoryMessageReplyMarkup::create(const MTPReplyMarkup &markup) { + flags = 0; + rows.clear(); + inlineKeyboard.clear(); + + switch (markup.type()) { + case mtpc_replyKeyboardMarkup: { + const MTPDreplyKeyboardMarkup &d(markup.c_replyKeyboardMarkup()); + flags = d.vflags.v; + + createFromButtonRows(d.vrows.c_vector().v); + } break; + + case mtpc_replyInlineMarkup: { + const MTPDreplyInlineMarkup &d(markup.c_replyInlineMarkup()); + flags = MTPDreplyKeyboardMarkup::Flags(0) | MTPDreplyKeyboardMarkup_ClientFlag::f_inline; + + createFromButtonRows(d.vrows.c_vector().v); + } break; + + case mtpc_replyKeyboardHide: { + const MTPDreplyKeyboardHide &d(markup.c_replyKeyboardHide()); + flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_zero; + } break; + + case mtpc_replyKeyboardForceReply: { + const MTPDreplyKeyboardForceReply &d(markup.c_replyKeyboardForceReply()); + flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply; + } break; + } +} + +void HistoryDependentItemCallback::call(ChannelData *channel, MsgId msgId) const { + if (HistoryItem *item = App::histItemById(_dependent)) { + item->updateDependencyItem(); + } +} + +void HistoryMessageUnreadBar::init(int count) { + if (_freezed) return; + _text = lng_unread_bar(lt_count, count); + _width = st::semiboldFont->width(_text); +} + +int HistoryMessageUnreadBar::height() { + return st::unreadBarHeight + st::unreadBarMargin; +} + +int HistoryMessageUnreadBar::marginTop() { + return st::lineWidth + st::unreadBarMargin; +} + +void HistoryMessageUnreadBar::paint(Painter &p, int y, int w) const { + p.fillRect(0, y + marginTop(), w, height() - marginTop() - st::lineWidth, st::unreadBarBG); + p.fillRect(0, y + height() - st::lineWidth, w, st::lineWidth, st::unreadBarBorder); + p.setFont(st::unreadBarFont); + p.setPen(st::unreadBarColor); + + int left = st::msgServiceMargin.left(); + int maxwidth = w; + if (Adaptive::Wide()) { + maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); + } + w = maxwidth; + + p.drawText((w - _width) / 2, y + marginTop() + (st::unreadBarHeight - 2 * st::lineWidth - st::unreadBarFont->height) / 2 + st::unreadBarFont->ascent, _text); +} + +void HistoryMessageDate::init(const QDateTime &date) { + _text = langDayOfMonthFull(date.date()); + _width = st::msgServiceFont->width(_text); +} + +int HistoryMessageDate::height() const { + return st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom() + st::msgServiceMargin.bottom(); +} + +void HistoryMessageDate::paint(Painter &p, int y, int w) const { + int left = st::msgServiceMargin.left(); + int maxwidth = w; + if (Adaptive::Wide()) { + maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); + } + w = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left(); + + left += (w - _width - st::msgServicePadding.left() - st::msgServicePadding.right()) / 2; + int height = st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom(); + App::roundRect(p, left, y + st::msgServiceMargin.top(), _width + st::msgServicePadding.left() + st::msgServicePadding.left(), height, App::msgServiceBg(), ServiceCorners); + + p.setFont(st::msgServiceFont); + p.setPen(st::msgServiceColor); + p.drawText(left + st::msgServicePadding.left(), y + st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->ascent, _text); +} + +void HistoryMediaPtr::reset(HistoryItem *host, HistoryMedia *p) { + if (_p) { + _p->detachFromItem(host); + delete _p; + } + _p = p; + if (_p) { + _p->attachToItem(host); + } +} + +HistoryItem::HistoryItem(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime msgDate, int32 from) : HistoryElem() +, y(0) , id(msgId) , date(msgDate) , _from(from ? App::user(from) : history->peer) -, _fromVersion(_from->nameVersion) , _history(history) -, _block(block) -, _flags(flags) -{ +, _flags(flags | MTPDmessage_ClientFlag::f_pending_init_dimensions | MTPDmessage_ClientFlag::f_pending_resize) +, _authorNameVersion(author()->nameVersion) { +} + +void HistoryItem::finishCreate() { + App::historyRegItem(this); +} + +void HistoryItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + if (auto *markup = Get()) { + if (markup->inlineKeyboard) { + markup->inlineKeyboard->clickHandlerActiveChanged(p, active); + } + } + App::hoveredLinkItem(active ? this : nullptr); + Ui::repaintHistoryItem(this); +} + +void HistoryItem::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { + if (auto *markup = Get()) { + if (markup->inlineKeyboard) { + markup->inlineKeyboard->clickHandlerPressedChanged(p, pressed); + } + } + App::pressedLinkItem(pressed ? this : nullptr); + Ui::repaintHistoryItem(this); } void HistoryItem::destroy() { @@ -2876,6 +3202,9 @@ void HistoryItem::destroy() { detach(); if (history()->isChannel()) { history()->asChannelHistory()->messageDeleted(this); + if (history()->peer->isMegagroup() && history()->peer->asChannel()->mgInfo->pinnedMsgId == id) { + history()->peer->asChannel()->mgInfo->pinnedMsgId = 0; + } } if (history()->lastMsg == this) { history()->fixLastMessage(wasAtBottom); @@ -2884,34 +3213,59 @@ void HistoryItem::destroy() { history()->clearLastKeyboard(); if (App::main()) App::main()->updateBotKeyboard(history()); } + if ((!out() || isPost()) && unread() && history()->unreadCount > 0) { + history()->setUnreadCount(history()->unreadCount - 1); + } + Global::RefPendingRepaintItems().remove(this); delete this; } void HistoryItem::detach() { - if (_history) { - if (_history->unreadBar == this) { - _history->unreadBar = 0; - } - if (_history->isChannel()) { - _history->asChannelHistory()->messageDetached(this); - } - } - if (_block) { - _block->removeItem(this); - detachFast(); - App::historyItemDetached(this); - } else { - if (_history->showFrom == this) { - _history->showFrom = 0; - } - } - if (_history && _history->unreadBar && _history->blocks.back()->items.back() == _history->unreadBar) { - _history->unreadBar->destroy(); + if (detached()) return; + + if (_history->isChannel()) { + _history->asChannelHistory()->messageDetached(this); } + _block->removeItem(this); + App::historyItemDetached(this); + + _history->setPendingResize(); } void HistoryItem::detachFast() { - _block = 0; + _block = nullptr; + _indexInBlock = -1; +} + +void HistoryItem::previousItemChanged() { + if (displayDate()) { + if (!Has()) { + AddComponents(HistoryMessageDate::Bit()); + Get()->init(date); + setPendingInitDimensions(); + } + } else if (Has()) { + RemoveComponents(HistoryMessageDate::Bit()); + setPendingInitDimensions(); + } + + recountAttachToPrevious(); +} + +void HistoryItem::recountAttachToPrevious() { + bool attach = false; + if (!isPost() && !Has() && !Has()) { + if (HistoryItem *prev = previous()) { + attach = !prev->isPost() && !prev->serviceMsg() && prev->from() == from() && qAbs(prev->date.secsTo(date)) < AttachMessageToPreviousSecondsDelta; + } + } + if (attach && !(_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) { + _flags |= MTPDmessage_ClientFlag::f_attach_to_previous; + setPendingInitDimensions(); + } else if (!attach && (_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) { + _flags &= ~MTPDmessage_ClientFlag::f_attach_to_previous; + setPendingInitDimensions(); + } } void HistoryItem::setId(MsgId newId) { @@ -2919,6 +3273,75 @@ void HistoryItem::setId(MsgId newId) { id = newId; } +bool HistoryItem::canEdit(const QDateTime &cur) const { + ChannelData *channel = _history->peer->asChannel(); + int32 s = date.secsTo(cur); + if (!channel || id < 0 || date.secsTo(cur) >= Global::EditTimeLimit()) return false; + + if (const HistoryMessage *msg = toHistoryMessage()) { + if (msg->Has() || msg->Has()) return false; + + if (HistoryMedia *media = msg->getMedia()) { + HistoryMediaType t = media->type(); + if (t != MediaTypePhoto && + t != MediaTypeVideo && + t != MediaTypeFile && + t != MediaTypeGif && + t != MediaTypeMusicFile && + t != MediaTypeVoiceFile && + t != MediaTypeWebPage) { + return false; + } + } + if (isPost()) { + return (channel->amCreator() || (channel->amEditor() && out())); + } + return out(); + } + return false; +} + +void HistoryItem::destroyUnreadBar() { + if (Has()) { + RemoveComponents(HistoryMessageUnreadBar::Bit()); + setPendingInitDimensions(); + if (_history->unreadBar == this) { + _history->unreadBar = nullptr; + } + + recountAttachToPrevious(); + } +} + +void HistoryItem::setUnreadBarCount(int count) { + if (count > 0) { + HistoryMessageUnreadBar *bar; + if (!Has()) { + AddComponents(HistoryMessageUnreadBar::Bit()); + setPendingInitDimensions(); + + recountAttachToPrevious(); + + bar = Get(); + } else { + bar = Get(); + if (bar->_freezed) { + return; + } + Global::RefPendingRepaintItems().insert(this); + } + bar->init(count); + } else { + destroyUnreadBar(); + } +} + +void HistoryItem::setUnreadBarFreezed() { + if (auto *bar = Get()) { + bar->_freezed = true; + } +} + void HistoryItem::clipCallback(ClipReaderNotification notification) { HistoryMedia *media = getMedia(); if (!media) return; @@ -2941,8 +3364,7 @@ void HistoryItem::clipCallback(ClipReaderNotification notification) { } } if (!stopped) { - initDimensions(); - Notify::historyItemResized(this); + setPendingInitDimensions(); Notify::historyItemLayoutChanged(this); } } break; @@ -2962,14 +3384,6 @@ HistoryItem::~HistoryItem() { } } -HistoryItem *regItem(HistoryItem *item) { - if (item) { - App::historyRegItem(item); - item->initDimensions(); - } - return item; -} - RadialAnimation::RadialAnimation(AnimationCreator creator) : _firstStart(0) , _lastStart(0) @@ -3009,7 +3423,7 @@ void RadialAnimation::update(float64 prg, bool finished, uint64 ms) { _opacity *= 1 - r; } float64 fromstart = fulldt / st::radialPeriod; - a_arcStart.update(fromstart - qFloor(fromstart), anim::linear); + a_arcStart.update(fromstart - std::floor(fromstart), anim::linear); } void RadialAnimation::stop() { @@ -3046,24 +3460,16 @@ void RadialAnimation::draw(Painter &p, const QRect &inner, int32 thickness, cons } namespace { - int32 videoMaxStatusWidth(VideoData *video) { - int32 result = st::normalFont->width(formatDownloadText(video->size, video->size)); - result = qMax(result, st::normalFont->width(formatDurationAndSizeText(video->duration, video->size))); - return result; - } - - int32 audioMaxStatusWidth(AudioData *audio) { - int32 result = st::normalFont->width(formatDownloadText(audio->size, audio->size)); - result = qMax(result, st::normalFont->width(formatPlayedText(audio->duration, audio->duration))); - result = qMax(result, st::normalFont->width(formatDurationAndSizeText(audio->duration, audio->size))); - return result; - } - int32 documentMaxStatusWidth(DocumentData *document) { int32 result = st::normalFont->width(formatDownloadText(document->size, document->size)); if (SongData *song = document->song()) { result = qMax(result, st::normalFont->width(formatPlayedText(song->duration, song->duration))); result = qMax(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size))); + } else if (VoiceData *voice = document->voice()) { + result = qMax(result, st::normalFont->width(formatPlayedText(voice->duration, voice->duration))); + result = qMax(result, st::normalFont->width(formatDurationAndSizeText(voice->duration, document->size))); + } else if (document->isVideo()) { + result = qMax(result, st::normalFont->width(formatDurationAndSizeText(document->duration(), document->size))); } else { result = qMax(result, st::normalFont->width(formatSizeText(document->size))); } @@ -3081,25 +3487,27 @@ HistoryFileMedia::HistoryFileMedia() : HistoryMedia() , _animation(0) { } -void HistoryFileMedia::linkOver(HistoryItem *parent, const TextLinkPtr &lnk) { - if ((lnk == _savel || lnk == _cancell) && !dataLoaded()) { - ensureAnimation(parent); - _animation->a_thumbOver.start(1); - _animation->_a_thumbOver.start(); +void HistoryFileMedia::clickHandlerActiveChanged(HistoryItem *parent, const ClickHandlerPtr &p, bool active) { + if (p == _savel || p == _cancell) { + if (active && !dataLoaded()) { + ensureAnimation(parent); + _animation->a_thumbOver.start(1); + _animation->_a_thumbOver.start(); + } else if (!active && _animation) { + _animation->a_thumbOver.start(0); + _animation->_a_thumbOver.start(); + } } } -void HistoryFileMedia::linkOut(HistoryItem *parent, const TextLinkPtr &lnk) { - if (_animation && (lnk == _savel || lnk == _cancell)) { - _animation->a_thumbOver.start(0); - _animation->_a_thumbOver.start(); - } +void HistoryFileMedia::clickHandlerPressedChanged(HistoryItem *parent, const ClickHandlerPtr &p, bool pressed) { + Ui::repaintHistoryItem(parent); } -void HistoryFileMedia::setLinks(ITextLink *openl, ITextLink *savel, ITextLink *cancell) { - _openl.reset(openl); - _savel.reset(savel); - _cancell.reset(cancell); +void HistoryFileMedia::setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell) { + _openl = std_::move(openl); + _savel = std_::move(savel); + _cancell = std_::move(cancell); } void HistoryFileMedia::setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const { @@ -3165,11 +3573,8 @@ HistoryFileMedia::~HistoryFileMedia() { HistoryPhoto::HistoryPhoto(PhotoData *photo, const QString &caption, const HistoryItem *parent) : HistoryFileMedia() , _data(photo) -, _pixw(1) -, _pixh(1) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { - setLinks(new PhotoLink(_data), new PhotoSaveLink(_data), new PhotoCancelLink(_data)); - + setLinks(MakeShared(_data), MakeShared(_data), MakeShared(_data)); if (!caption.isEmpty()) { _caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); } @@ -3177,10 +3582,8 @@ HistoryPhoto::HistoryPhoto(PhotoData *photo, const QString &caption, const Histo } HistoryPhoto::HistoryPhoto(PeerData *chat, const MTPDphoto &photo, int32 width) : HistoryFileMedia() -, _data(App::feedPhoto(photo)) -, _pixw(1) -, _pixh(1) { - setLinks(new PhotoLink(_data, chat), new PhotoSaveLink(_data, chat), new PhotoCancelLink(_data)); +, _data(App::feedPhoto(photo)) { + setLinks(MakeShared(_data, chat), MakeShared(_data, chat), MakeShared(_data, chat)); _width = width; init(); @@ -3191,7 +3594,7 @@ HistoryPhoto::HistoryPhoto(const HistoryPhoto &other) : HistoryFileMedia() , _pixw(other._pixw) , _pixh(other._pixh) , _caption(other._caption) { - setLinks(new PhotoLink(_data), new PhotoSaveLink(_data), new PhotoCancelLink(_data)); + setLinks(MakeShared(_data), MakeShared(_data), MakeShared(_data)); init(); } @@ -3238,10 +3641,10 @@ void HistoryPhoto::initDimensions(const HistoryItem *parent) { } } -int32 HistoryPhoto::resize(int32 width, const HistoryItem *parent) { +int HistoryPhoto::resizeGetHeight(int width, const HistoryItem *parent) { bool bubble = parent->hasBubble(); - int32 tw = convertScale(_data->full->width()), th = convertScale(_data->full->height()); + int tw = convertScale(_data->full->width()), th = convertScale(_data->full->height()); if (tw > st::maxMediaSize) { th = (st::maxMediaSize * th) / tw; tw = st::maxMediaSize; @@ -3268,14 +3671,14 @@ int32 HistoryPhoto::resize(int32 width, const HistoryItem *parent) { if (_pixw < 1) _pixw = 1; if (_pixh < 1) _pixh = 1; - int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + int minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); _width = qMax(_pixw, int16(minWidth)); _height = qMax(_pixh, int16(st::minPhotoSize)); if (bubble) { _width += st::mediaPadding.left() + st::mediaPadding.right(); _height += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_caption.isEmpty()) { - int32 captionw = _width - st::msgPadding.left() - st::msgPadding.right(); + int captionw = _width - st::msgPadding.left() - st::msgPadding.right(); _height += st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); } } @@ -3289,11 +3692,11 @@ void HistoryPhoto::draw(Painter &p, const HistoryItem *parent, const QRect &r, b bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); bool notChild = (parent->getMedia() == this); - int32 skipx = 0, skipy = 0, width = _width, height = _height; + int skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = parent->hasBubble(); - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; - int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); + int captionw = width - st::msgPadding.left() - st::msgPadding.right(); if (displayLoading) { ensureAnimation(parent); @@ -3339,7 +3742,7 @@ void HistoryPhoto::draw(Painter &p, const HistoryItem *parent, const QRect &r, b p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); p.setBrush(st::black); } else { - bool over = textlnkDrawOver(_data->loading() ? _cancell : _savel); + bool over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); } @@ -3373,7 +3776,7 @@ void HistoryPhoto::draw(Painter &p, const HistoryItem *parent, const QRect &r, b // date if (_caption.isEmpty()) { - if (notChild) { + if (notChild && (_data->uploading() || App::hoveredItem() == parent)) { int32 fullRight = skipx + width, fullBottom = skipy + height; parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); } @@ -3383,16 +3786,16 @@ void HistoryPhoto::draw(Painter &p, const HistoryItem *parent, const QRect &r, b } } -void HistoryPhoto::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { +void HistoryPhoto::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y, const HistoryItem *parent) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; - int32 skipx = 0, skipy = 0, width = _width, height = _height; + int skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = parent->hasBubble(); if (bubble) { skipx = st::mediaPadding.left(); skipy = st::mediaPadding.top(); if (!_caption.isEmpty()) { - int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); + int captionw = width - st::msgPadding.left() - st::msgPadding.right(); height -= _caption.countHeight(captionw) + st::msgPadding.bottom(); if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) { bool inText = false; @@ -3429,7 +3832,7 @@ void HistoryPhoto::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x } } -void HistoryPhoto::updateFrom(const MTPMessageMedia &media, HistoryItem *parent, bool allowEmitResize) { +void HistoryPhoto::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { if (media.type() == mtpc_messageMediaPhoto) { const MTPPhoto &photo(media.c_messageMediaPhoto().vphoto); App::feedPhoto(photo, _data); @@ -3477,11 +3880,11 @@ void HistoryPhoto::updateFrom(const MTPMessageMedia &media, HistoryItem *parent, } } -void HistoryPhoto::regItem(HistoryItem *item) { +void HistoryPhoto::attachToItem(HistoryItem *item) { App::regPhotoItem(_data, item); } -void HistoryPhoto::unregItem(HistoryItem *item) { +void HistoryPhoto::detachFromItem(HistoryItem *item) { App::unregPhotoItem(_data, item); } @@ -3497,15 +3900,15 @@ ImagePtr HistoryPhoto::replyPreview() { return _data->makeReplyPreview(); } -HistoryVideo::HistoryVideo(const MTPDvideo &video, const QString &caption, HistoryItem *parent) : HistoryFileMedia() -, _data(App::feedVideo(video)) +HistoryVideo::HistoryVideo(DocumentData *document, const QString &caption, const HistoryItem *parent) : HistoryFileMedia() +, _data(document) , _thumbw(1) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { if (!caption.isEmpty()) { _caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); } - setLinks(new VideoOpenLink(_data), new VideoSaveLink(_data), new VideoCancelLink(_data)); + setDocumentLinks(_data); setStatusSize(FileStatusSizeReady); @@ -3516,7 +3919,7 @@ HistoryVideo::HistoryVideo(const HistoryVideo &other) : HistoryFileMedia() , _data(other._data) , _thumbw(other._thumbw) , _caption(other._caption) { - setLinks(new VideoOpenLink(_data), new VideoSaveLink(_data), new VideoCancelLink(_data)); + setDocumentLinks(_data); setStatusSize(other._statusSize); } @@ -3542,8 +3945,8 @@ void HistoryVideo::initDimensions(const HistoryItem *parent) { _thumbw = qMax(tw, 1); int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - minWidth = qMax(minWidth, videoMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); - _maxw = qMax(_thumbw, int16(minWidth)); + minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + _maxw = qMax(_thumbw, int32(minWidth)); _minh = qMax(th, int32(st::minPhotoSize)); if (bubble) { _maxw += st::mediaPadding.left() + st::mediaPadding.right(); @@ -3554,10 +3957,10 @@ void HistoryVideo::initDimensions(const HistoryItem *parent) { } } -int32 HistoryVideo::resize(int32 width, const HistoryItem *parent) { +int HistoryVideo::resizeGetHeight(int width, const HistoryItem *parent) { bool bubble = parent->hasBubble(); - int32 tw = convertScale(_data->thumb->width()), th = convertScale(_data->thumb->height()); + int tw = convertScale(_data->thumb->width()), th = convertScale(_data->thumb->height()); if (!tw || !th) { tw = th = 1; } @@ -3577,15 +3980,16 @@ int32 HistoryVideo::resize(int32 width, const HistoryItem *parent) { tw = width; } - int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - minWidth = qMax(minWidth, videoMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); - _width = qMax(_thumbw, int16(minWidth)); - _height = qMax(th, int32(st::minPhotoSize)); + _thumbw = qMax(tw, 1); + int minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * int(st::msgDateImgDelta + st::msgDateImgPadding.x())); + _width = qMax(_thumbw, int(minWidth)); + _height = qMax(th, int(st::minPhotoSize)); if (bubble) { _width += st::mediaPadding.left() + st::mediaPadding.right(); _height += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_caption.isEmpty()) { - int32 captionw = _width - st::msgPadding.left() - st::msgPadding.right(); + int captionw = _width - st::msgPadding.left() - st::msgPadding.right(); _height += st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); } } @@ -3598,11 +4002,11 @@ void HistoryVideo::draw(Painter &p, const HistoryItem *parent, const QRect &r, b _data->automaticLoad(parent); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); - int32 skipx = 0, skipy = 0, width = _width, height = _height; + int skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = parent->hasBubble(); - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; - int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); + int captionw = width - st::msgPadding.left() - st::msgPadding.right(); if (displayLoading) { ensureAnimation(parent); @@ -3627,9 +4031,7 @@ void HistoryVideo::draw(Painter &p, const HistoryItem *parent, const QRect &r, b } QRect rthumb(rtlrect(skipx, skipy, width, height, _width)); - - QPixmap pix = _data->thumb->pixBlurredSingle(_thumbw, 0, width, height); - p.drawPixmap(rthumb.topLeft(), pix); + p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_thumbw, 0, width, height)); if (selected) { App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); } @@ -3643,7 +4045,7 @@ void HistoryVideo::draw(Painter &p, const HistoryItem *parent, const QRect &r, b p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); p.setBrush(st::black); } else { - bool over = textlnkDrawOver(_data->loading() ? _cancell : _savel); + bool over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); } @@ -3689,7 +4091,7 @@ void HistoryVideo::draw(Painter &p, const HistoryItem *parent, const QRect &r, b } } -void HistoryVideo::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { +void HistoryVideo::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y, const HistoryItem *parent) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; bool loaded = _data->loaded(); @@ -3727,7 +4129,7 @@ void HistoryVideo::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x } void HistoryVideo::setStatusSize(int32 newSize) const { - HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration, 0); + HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration(), 0); } const QString HistoryVideo::inDialogsText() const { @@ -3747,7 +4149,7 @@ void HistoryVideo::updateStatusText(const HistoryItem *parent) const { statusSize = _data->uploadOffset; } else if (_data->loading()) { statusSize = _data->loadOffset(); - } else if (!_data->already().isEmpty()) { + } else if (_data->loaded()) { statusSize = FileStatusSizeLoaded; } else { statusSize = FileStatusSizeReady; @@ -3757,18 +4159,18 @@ void HistoryVideo::updateStatusText(const HistoryItem *parent) const { } } -void HistoryVideo::regItem(HistoryItem *item) { - App::regVideoItem(_data, item); +void HistoryVideo::attachToItem(HistoryItem *item) { + App::regDocumentItem(_data, item); } -void HistoryVideo::unregItem(HistoryItem *item) { - App::unregVideoItem(_data, item); +void HistoryVideo::detachFromItem(HistoryItem *item) { + App::unregDocumentItem(_data, item); } ImagePtr HistoryVideo::replyPreview() { if (_data->replyPreview->isNull() && !_data->thumb->isNull()) { if (_data->thumb->loaded()) { - int w = _data->thumb->width(), h = _data->thumb->height(); + int w = convertScale(_data->thumb->width()), h = convertScale(_data->thumb->height()); if (w <= 0) w = 1; if (h <= 0) h = 1; _data->replyPreview = ImagePtr(w > h ? _data->thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : _data->thumb->pix(st::msgReplyBarSize.height()), "PNG"); @@ -3779,290 +4181,155 @@ ImagePtr HistoryVideo::replyPreview() { return _data->replyPreview; } -HistoryAudio::HistoryAudio(const MTPDaudio &audio) : HistoryFileMedia() -, _data(App::feedAudio(audio)) { - setLinks(new AudioOpenLink(_data), new AudioOpenLink(_data), new AudioCancelLink(_data)); - - setStatusSize(FileStatusSizeReady); +HistoryDocumentVoicePlayback::HistoryDocumentVoicePlayback(const HistoryDocument *that) +: _position(0) +, a_progress(0., 0.) +, _a_progress(animation(const_cast(that), &HistoryDocument::step_voiceProgress)) { } -HistoryAudio::HistoryAudio(const HistoryAudio &other) : HistoryFileMedia() -, _data(other._data) { - setLinks(new AudioOpenLink(_data), new AudioOpenLink(_data), new AudioCancelLink(_data)); - - setStatusSize(other._statusSize); -} - -void HistoryAudio::initDimensions(const HistoryItem *parent) { - _maxw = st::msgFileMinWidth; - - int32 tleft = 0, tright = 0; - - tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); - tright = st::msgFileThumbPadding.left(); - _maxw = qMax(_maxw, tleft + audioMaxStatusWidth(_data) + int(st::mediaUnreadSkip + st::mediaUnreadSize) + parent->skipBlockWidth() + st::msgPadding.right()); - - _maxw = qMax(tleft + st::semiboldFont->width(lang(lng_media_audio)) + tright, _maxw); - _maxw = qMin(_maxw, int(st::msgMaxWidth)); - - _height = _minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); -} - -void HistoryAudio::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; - - _data->automaticLoad(parent); - bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); - - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; - - if (displayLoading) { - ensureAnimation(parent); - if (!_animation->radial.animating()) { - _animation->radial.start(_data->progress()); - } - } - bool showPause = updateStatusText(parent); - bool radial = isRadialAnimation(ms); - - int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; - - nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); - nametop = st::msgFileNameTop; - nameright = st::msgFilePadding.left(); - statustop = st::msgFileStatusTop; - - QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width)); - p.setPen(Qt::NoPen); - if (selected) { - p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected); - } else if (isThumbAnimation(ms)) { - float64 over = _animation->a_thumbOver.current(); - p.setBrush(style::interpolate(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over)); - } else { - bool over = textlnkDrawOver(_data->loading() ? _cancell : _savel); - p.setBrush(outbg ? (over ? st::msgFileOutBgOver : st::msgFileOutBg) : (over ? st::msgFileInBgOver : st::msgFileInBg)); - } - - p.setRenderHint(QPainter::HighQualityAntialiasing); - p.drawEllipse(inner); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - - if (radial) { - QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); - style::color bg(outbg ? (selected ? st::msgOutBgSelected : st::msgOutBg) : (selected ? st::msgInBgSelected : st::msgInBg)); - _animation->radial.draw(p, rinner, st::msgFileRadialLine, bg); - } - - style::sprite icon; - if (showPause) { - icon = outbg ? (selected ? st::msgFileOutPauseSelected : st::msgFileOutPause) : (selected ? st::msgFileInPauseSelected : st::msgFileInPause); - } else if (radial || _data->loading()) { - icon = outbg ? (selected ? st::msgFileOutCancelSelected : st::msgFileOutCancel) : (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); - } else if (loaded) { - icon = outbg ? (selected ? st::msgFileOutPlaySelected : st::msgFileOutPlay) : (selected ? st::msgFileInPlaySelected : st::msgFileInPlay); - } else { - icon = outbg ? (selected ? st::msgFileOutDownloadSelected : st::msgFileOutDownload) : (selected ? st::msgFileInDownloadSelected : st::msgFileInDownload); - } - p.drawSpriteCenter(inner, icon); - - int32 namewidth = _width - nameleft - nameright; - - p.setFont(st::semiboldFont); - p.setPen(st::black); - p.drawTextLeft(nameleft, nametop, _width, lang(lng_media_audio)); - - style::color status(outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg)); - p.setFont(st::normalFont); - p.setPen(status); - p.drawTextLeft(nameleft, statustop, _width, _statusText); - - if (parent->isMediaUnread()) { - int32 w = st::normalFont->width(_statusText); - if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= namewidth) { - p.setPen(Qt::NoPen); - p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg)); - - p.setRenderHint(QPainter::HighQualityAntialiasing, true); - p.drawEllipse(rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, _width)); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - } +void HistoryDocumentVoice::ensurePlayback(const HistoryDocument *that) const { + if (!_playback) { + _playback = new HistoryDocumentVoicePlayback(that); } } -void HistoryAudio::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; - - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; - bool loaded = _data->loaded(); - - bool showPause = updateStatusText(parent); - - int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; - - QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width)); - if ((_data->loading() || _data->status == FileUploading || !loaded) && inner.contains(x, y)) { - lnk = (_data->loading() || _data->status == FileUploading) ? _cancell : _savel; - return; +void HistoryDocumentVoice::checkPlaybackFinished() const { + if (_playback && !_playback->_a_progress.animating()) { + delete _playback; + _playback = nullptr; } - - if (x >= 0 && y >= 0 && x < _width && y < _height && _data->access && !_data->loading()) { - lnk = _openl; - return; - } -} - -const QString HistoryAudio::inDialogsText() const { - return lang(lng_in_dlg_audio); -} - -const QString HistoryAudio::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_audio) + qsl(" ]"); -} - -void HistoryAudio::regItem(HistoryItem *item) { - App::regAudioItem(_data, item); -} - -void HistoryAudio::unregItem(HistoryItem *item) { - App::unregAudioItem(_data, item); -} - -void HistoryAudio::updateFrom(const MTPMessageMedia &media, HistoryItem *parent, bool allowEmitResize) { - if (media.type() == mtpc_messageMediaAudio) { - App::feedAudio(media.c_messageMediaAudio().vaudio, _data); - if (!_data->data().isEmpty()) { - Local::writeAudio(mediaKey(AudioFileLocation, _data->dc, _data->id), _data->data()); - } - } -} - -void HistoryAudio::setStatusSize(int32 newSize, qint64 realDuration) const { - HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration, realDuration); -} - -bool HistoryAudio::updateStatusText(const HistoryItem *parent) const { - bool showPause = false; - int32 statusSize = 0, realDuration = 0; - if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { - statusSize = FileStatusSizeFailed; - } else if (_data->status == FileUploading) { - statusSize = _data->uploadOffset; - } else if (_data->loading()) { - statusSize = _data->loadOffset(); - } else if (_data->loaded()) { - AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; - if (audioPlayer()) { - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); - } - - if (playing.msgId == parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); - realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); - showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); - } else { - statusSize = FileStatusSizeLoaded; - } - } else { - statusSize = FileStatusSizeReady; - } - if (statusSize != _statusSize) { - setStatusSize(statusSize, realDuration); - } - return showPause; } HistoryDocument::HistoryDocument(DocumentData *document, const QString &caption, const HistoryItem *parent) : HistoryFileMedia() -, _data(document) -, _linksavel(new DocumentSaveLink(_data)) -, _linkcancell(new DocumentCancelLink(_data)) -, _name(documentName(_data)) -, _namew(st::semiboldFont->width(_name)) -, _caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) { - setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data)); +, _parent(nullptr) +, _data(document) { + createComponents(!caption.isEmpty()); + if (auto *named = Get()) { + named->_name = documentName(_data); + named->_namew = st::semiboldFont->width(named->_name); + } + + setDocumentLinks(_data); setStatusSize(FileStatusSizeReady); - if (!caption.isEmpty()) { - _caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); - } - - if (withThumb()) { - _data->thumb->load(); - int32 tw = _data->thumb->width(), th = _data->thumb->height(); - if (tw > th) { - _thumbw = (tw * st::msgFileThumbSize) / th; - } else { - _thumbw = st::msgFileThumbSize; - } - } else { - _thumbw = 0; + if (auto *captioned = Get()) { + captioned->_caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); } } HistoryDocument::HistoryDocument(const HistoryDocument &other) : HistoryFileMedia() -, _data(other._data) -, _linksavel(new DocumentSaveLink(_data)) -, _linkcancell(new DocumentCancelLink(_data)) -, _name(other._name) -, _namew(other._namew) -, _thumbw(other._thumbw) { - setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data)); +, Composer() +, _parent(nullptr) +, _data(other._data) { + auto *captioned = other.Get(); + createComponents(captioned != 0); + if (auto *named = Get()) { + if (auto *othernamed = other.Get()) { + named->_name = othernamed->_name; + named->_namew = othernamed->_namew; + } else { + named->_name = documentName(_data); + named->_namew = st::semiboldFont->width(named->_name); + } + } + + setDocumentLinks(_data); setStatusSize(other._statusSize); + + if (captioned) { + Get()->_caption = captioned->_caption; + } +} + +void HistoryDocument::createComponents(bool caption) { + uint64 mask = 0; + if (_data->voice()) { + mask |= HistoryDocumentVoice::Bit(); + } else { + mask |= HistoryDocumentNamed::Bit(); + if (!_data->song() && !_data->thumb->isNull() && _data->thumb->width() && _data->thumb->height()) { + mask |= HistoryDocumentThumbed::Bit(); + } + } + if (caption) { + mask |= HistoryDocumentCaptioned::Bit(); + } + UpdateComponents(mask); + if (auto *thumbed = Get()) { + thumbed->_linksavel.reset(new DocumentSaveClickHandler(_data)); + thumbed->_linkcancell.reset(new DocumentCancelClickHandler(_data)); + } } void HistoryDocument::initDimensions(const HistoryItem *parent) { - if (_caption.hasSkipBlock()) { - _caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); + _parent = parent; + + auto *captioned = Get(); + if (captioned && captioned->_caption.hasSkipBlock()) { + captioned->_caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); + } + + auto *thumbed = Get(); + if (thumbed) { + _data->thumb->load(); + int32 tw = convertScale(_data->thumb->width()), th = convertScale(_data->thumb->height()); + if (tw > th) { + thumbed->_thumbw = (tw * st::msgFileThumbSize) / th; + } else { + thumbed->_thumbw = st::msgFileThumbSize; + } } _maxw = st::msgFileMinWidth; int32 tleft = 0, tright = 0; - bool wthumb = withThumb(); - if (wthumb) { + if (thumbed) { tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); tright = st::msgFileThumbPadding.left(); _maxw = qMax(_maxw, tleft + documentMaxStatusWidth(_data) + tright); } else { tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); tright = st::msgFileThumbPadding.left(); - _maxw = qMax(_maxw, tleft + documentMaxStatusWidth(_data) + parent->skipBlockWidth() + st::msgPadding.right()); + int32 unread = _data->voice() ? (st::mediaUnreadSkip + st::mediaUnreadSize) : 0; + _maxw = qMax(_maxw, tleft + documentMaxStatusWidth(_data) + unread + parent->skipBlockWidth() + st::msgPadding.right()); } - _maxw = qMax(tleft + _namew + tright, _maxw); - _maxw = qMin(_maxw, int(st::msgMaxWidth)); + if (auto *named = Get()) { + _maxw = qMax(tleft + named->_namew + tright, _maxw); + _maxw = qMin(_maxw, int(st::msgMaxWidth)); + } - if (wthumb) { + if (thumbed) { _minh = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); + if (!captioned && parent->Has()) { + _minh += st::msgDateFont->height - st::msgDateDelta.y(); + } } else { _minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); } - if (_caption.isEmpty()) { - _height = _minh; + if (captioned) { + _minh += captioned->_caption.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); } else { - _minh += _caption.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + _height = _minh; } } -int32 HistoryDocument::resize(int32 width, const HistoryItem *parent) { - if (_caption.isEmpty()) { - return HistoryFileMedia::resize(width, parent); +int HistoryDocument::resizeGetHeight(int width, const HistoryItem *parent) { + auto *captioned = Get(); + if (!captioned) { + return HistoryFileMedia::resizeGetHeight(width, parent); } _width = qMin(width, _maxw); - bool wthumb = withThumb(); - if (wthumb) { + if (Get()) { _height = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); } else { _height = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); } - _height += _caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + _height += captioned->_caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); return _height; } @@ -4073,9 +4340,9 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r _data->automaticLoad(parent); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); - int32 captionw = _width - st::msgPadding.left() - st::msgPadding.right(); + int captionw = _width - st::msgPadding.left() - st::msgPadding.right(); - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; if (displayLoading) { ensureAnimation(parent); @@ -4086,9 +4353,8 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r bool showPause = updateStatusText(parent); bool radial = isRadialAnimation(ms); - int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; - bool wthumb = withThumb(); - if (wthumb) { + int nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; + if (auto *thumbed = Get()) { nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); nametop = st::msgFileThumbNameTop; nameright = st::msgFileThumbPadding.left(); @@ -4097,12 +4363,8 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, _width)); - if (_data->thumb->loaded()) { - QPixmap thumb = loaded ? _data->thumb->pixSingle(_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize) : _data->thumb->pixBlurredSingle(_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize); - p.drawPixmap(rthumb.topLeft(), thumb); - } else { - App::roundRect(p, rthumb, st::black, BlackCorners); - } + QPixmap thumb = loaded ? _data->thumb->pixSingle(thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize) : _data->thumb->pixBlurredSingle(thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize); + p.drawPixmap(rthumb.topLeft(), thumb); if (selected) { App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); } @@ -4118,7 +4380,7 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); p.setBrush(st::black); } else { - bool over = textlnkDrawOver(_data->loading() ? _cancell : _savel); + bool over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); } p.setOpacity(radialOpacity * p.opacity()); @@ -4145,11 +4407,11 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r } if (_data->status != FileUploadFailed) { - const TextLinkPtr &lnk((_data->loading() || _data->status == FileUploading) ? _linkcancell : _linksavel); - bool over = textlnkDrawOver(lnk); + const ClickHandlerPtr &lnk((_data->loading() || _data->status == FileUploading) ? thumbed->_linkcancell : thumbed->_linksavel); + bool over = ClickHandler::showAsActive(lnk); p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont); p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg)); - p.drawTextLeft(nameleft, linktop, _width, _link, _linkw); + p.drawTextLeft(nameleft, linktop, _width, thumbed->_link, thumbed->_linkw); } } else { nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); @@ -4166,7 +4428,7 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r float64 over = _animation->a_thumbOver.current(); p.setBrush(style::interpolate(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over)); } else { - bool over = textlnkDrawOver(_data->loading() ? _cancell : _savel); + bool over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(outbg ? (over ? st::msgFileOutBgOver : st::msgFileOutBg) : (over ? st::msgFileInBgOver : st::msgFileInBg)); } @@ -4186,7 +4448,7 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r } else if (radial || _data->loading()) { icon = outbg ? (selected ? st::msgFileOutCancelSelected : st::msgFileOutCancel) : (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); } else if (loaded) { - if (_data->song()) { + if (_data->song() || _data->voice()) { icon = outbg ? (selected ? st::msgFileOutPlaySelected : st::msgFileOutPlay) : (selected ? st::msgFileInPlaySelected : st::msgFileInPlay); } else if (_data->isImage()) { icon = outbg ? (selected ? st::msgFileOutImageSelected : st::msgFileOutImage) : (selected ? st::msgFileInImageSelected : st::msgFileInImage); @@ -4200,12 +4462,73 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r } int32 namewidth = _width - nameleft - nameright; - p.setFont(st::semiboldFont); - p.setPen(st::black); - if (namewidth < _namew) { - p.drawTextLeft(nameleft, nametop, _width, st::semiboldFont->elided(_name, namewidth)); - } else { - p.drawTextLeft(nameleft, nametop, _width, _name, _namew); + if (auto *voice = Get()) { + const VoiceWaveform *wf = 0; + uchar norm_value = 0; + if (_data->voice()) { + wf = &_data->voice()->waveform; + if (wf->isEmpty()) { + wf = 0; + if (loaded) { + Local::countVoiceWaveform(_data); + } + } else if (wf->at(0) < 0) { + wf = 0; + } else { + norm_value = _data->voice()->wavemax; + } + } + float64 prg = voice->_playback ? voice->_playback->a_progress.current() : 0; + + // rescale waveform by going in waveform.size * bar_count 1D grid + style::color active(outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive)); + style::color inactive(outbg ? (selected ? st::msgWaveformOutInactiveSelected : st::msgWaveformOutInactive) : (selected ? st::msgWaveformInInactiveSelected : st::msgWaveformInInactive)); + int32 wf_size = wf ? wf->size() : WaveformSamplesCount, availw = int32(namewidth + st::msgWaveformSkip), activew = qRound(availw * prg); + if (!outbg && !voice->_playback && parent->isMediaUnread()) { + activew = availw; + } + int32 bar_count = qMin(availw / int32(st::msgWaveformBar + st::msgWaveformSkip), wf_size); + uchar max_value = 0; + int32 max_delta = st::msgWaveformMax - st::msgWaveformMin, bottom = st::msgFilePadding.top() + st::msgWaveformMax; + p.setPen(Qt::NoPen); + for (int32 i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) { + uchar value = wf ? wf->at(i) : 0; + if (sum_i + bar_count >= wf_size) { // draw bar + sum_i = sum_i + bar_count - wf_size; + if (sum_i < (bar_count + 1) / 2) { + if (max_value < value) max_value = value; + } + int32 bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1); + + if (bar_x >= activew) { + p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive); + } else if (bar_x + st::msgWaveformBar <= activew) { + p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active); + } else { + p.fillRect(nameleft + bar_x, bottom - bar_value, activew - bar_x, st::msgWaveformMin + bar_value, active); + p.fillRect(nameleft + activew, bottom - bar_value, st::msgWaveformBar - (activew - bar_x), st::msgWaveformMin + bar_value, inactive); + } + bar_x += st::msgWaveformBar + st::msgWaveformSkip; + + if (sum_i < (bar_count + 1) / 2) { + max_value = 0; + } else { + max_value = value; + } + } else { + if (max_value < value) max_value = value; + + sum_i += bar_count; + } + } + } else if (auto *named = Get()) { + p.setFont(st::semiboldFont); + p.setPen(st::black); + if (namewidth < named->_namew) { + p.drawTextLeft(nameleft, nametop, _width, st::semiboldFont->elided(named->_name, namewidth)); + } else { + p.drawTextLeft(nameleft, nametop, _width, named->_name, named->_namew); + } } style::color status(outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg)); @@ -4213,23 +4536,34 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r p.setPen(status); p.drawTextLeft(nameleft, statustop, _width, _statusText); - if (!_caption.isEmpty()) { + if (parent->isMediaUnread()) { + int32 w = st::normalFont->width(_statusText); + if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= namewidth) { + p.setPen(Qt::NoPen); + p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg)); + + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + p.drawEllipse(rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, _width)); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + } + } + + if (auto *captioned = Get()) { p.setPen(st::black); - _caption.draw(p, st::msgPadding.left(), bottom, captionw); + captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw); } } -void HistoryDocument::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { +void HistoryDocument::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y, const HistoryItem *parent) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; bool loaded = _data->loaded(); bool showPause = updateStatusText(parent); int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; - bool wthumb = withThumb(); - if (wthumb) { + if (auto *thumbed = Get()) { nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); linktop = st::msgFileThumbLinkTop; bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); @@ -4242,8 +4576,8 @@ void HistoryDocument::getState(TextLinkPtr &lnk, HistoryCursorState &state, int3 } if (_data->status != FileUploadFailed) { - if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, _width).contains(x, y)) { - lnk = (_data->loading() || _data->uploading()) ? _linkcancell : _linksavel; + if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, _width).contains(x, y)) { + lnk = (_data->loading() || _data->uploading()) ? thumbed->_linkcancell : thumbed->_linksavel; return; } } @@ -4258,14 +4592,14 @@ void HistoryDocument::getState(TextLinkPtr &lnk, HistoryCursorState &state, int3 } int32 height = _height; - if (!_caption.isEmpty()) { + if (auto *captioned = Get()) { if (y >= bottom) { bool inText = false; - _caption.getState(lnk, inText, x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right()); + captioned->_caption.getState(lnk, inText, x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right()); state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; return; } - height -= _caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + height -= captioned->_caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); } if (x >= 0 && y >= 0 && x < _width && y < height && !_data->loading() && !_data->uploading() && _data->access) { lnk = _openl; @@ -4274,28 +4608,62 @@ void HistoryDocument::getState(TextLinkPtr &lnk, HistoryCursorState &state, int3 } const QString HistoryDocument::inDialogsText() const { - return (_name.isEmpty() ? lang(lng_in_dlg_file) : _name) + (_caption.isEmpty() ? QString() : (' ' + _caption.original(0, 0xFFFF, Text::ExpandLinksNone))); + QString result; + if (Has()) { + result = lang(lng_in_dlg_audio); + } else if (_data->song()) { + result = lang(lng_in_dlg_audio_file); + } else { + auto *named = Get(); + result = (!named || named->_name.isEmpty()) ? lang(lng_in_dlg_file) : named->_name; + } + if (auto *captioned = Get()) { + if (!captioned->_caption.isEmpty()) { + result.append(' ').append(captioned->_caption.original(0, 0xFFFF, Text::ExpandLinksNone)); + } + } + return result; } const QString HistoryDocument::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_file) + (_name.isEmpty() ? QString() : (qsl(" : ") + _name)) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]"); + QString result; + if (Has()) { + result = lang(lng_in_dlg_audio); + } else if (_data->song()) { + result = lang(lng_in_dlg_audio_file); + } else { + result = lang(lng_in_dlg_file); + } + if (auto *named = Get()) { + if (!named->_name.isEmpty()) { + result.append(qsl(" : ")).append(named->_name); + } + } + if (auto *captioned = Get()) { + if (!captioned->_caption.isEmpty()) { + result.append(qsl(", ")).append(captioned->_caption.original(0, 0xFFFF, Text::ExpandLinksAll)); + } + } + return qsl("[ ") + result.append(qsl(" ]")); } void HistoryDocument::setStatusSize(int32 newSize, qint64 realDuration) const { - HistoryFileMedia::setStatusSize(newSize, _data->size, _data->song() ? _data->song()->duration : -1, realDuration); - - if (_statusSize == FileStatusSizeReady) { - _link = lang(lng_media_download).toUpper(); - } else if (_statusSize == FileStatusSizeLoaded) { - _link = lang(lng_media_open_with).toUpper(); - } else if (_statusSize == FileStatusSizeFailed) { - _link = lang(lng_media_download).toUpper(); - } else if (_statusSize >= 0) { - _link = lang(lng_media_cancel).toUpper(); - } else { - _link = lang(lng_media_open_with).toUpper(); + int32 duration = _data->song() ? _data->song()->duration : (_data->voice() ? _data->voice()->duration : -1); + HistoryFileMedia::setStatusSize(newSize, _data->size, duration, realDuration); + if (auto *thumbed = Get()) { + if (_statusSize == FileStatusSizeReady) { + thumbed->_link = lang(lng_media_download).toUpper(); + } else if (_statusSize == FileStatusSizeLoaded) { + thumbed->_link = lang(lng_media_open_with).toUpper(); + } else if (_statusSize == FileStatusSizeFailed) { + thumbed->_link = lang(lng_media_download).toUpper(); + } else if (_statusSize >= 0) { + thumbed->_link = lang(lng_media_cancel).toUpper(); + } else { + thumbed->_link = lang(lng_media_open_with).toUpper(); + } + thumbed->_linkw = st::semiboldFont->width(thumbed->_link); } - _linkw = st::semiboldFont->width(_link); } bool HistoryDocument::updateStatusText(const HistoryItem *parent) const { @@ -4308,7 +4676,41 @@ bool HistoryDocument::updateStatusText(const HistoryItem *parent) const { } else if (_data->loading()) { statusSize = _data->loadOffset(); } else if (_data->loaded()) { - if (_data->song()) { + if (_data->voice()) { + AudioMsgId playing; + AudioPlayerState playingState = AudioPlayerStopped; + int64 playingPosition = 0, playingDuration = 0; + int32 playingFrequency = 0; + if (audioPlayer()) { + audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + } + + if (playing.msgId == parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + if (auto *voice = Get()) { + bool was = voice->_playback; + voice->ensurePlayback(this); + if (!was || playingPosition != voice->_playback->_position) { + float64 prg = playingDuration ? snap(float64(playingPosition) / playingDuration, 0., 1.) : 0.; + if (voice->_playback->_position < playingPosition) { + voice->_playback->a_progress.start(prg); + } else { + voice->_playback->a_progress = anim::fvalue(0., prg); + } + voice->_playback->_position = playingPosition; + voice->_playback->_a_progress.start(); + } + } + + statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); + realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); + showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); + } else { + statusSize = FileStatusSizeLoaded; + if (auto *voice = Get()) { + voice->checkPlaybackFinished(); + } + } + } else if (_data->song()) { SongMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; int64 playingPosition = 0, playingDuration = 0; @@ -4339,17 +4741,39 @@ bool HistoryDocument::updateStatusText(const HistoryItem *parent) const { return showPause; } -void HistoryDocument::regItem(HistoryItem *item) { +void HistoryDocument::step_voiceProgress(float64 ms, bool timer) { + if (auto *voice = Get()) { + if (voice->_playback) { + float64 dt = ms / (2 * AudioVoiceMsgUpdateView); + if (dt >= 1) { + voice->_playback->_a_progress.stop(); + voice->_playback->a_progress.finish(); + } else { + voice->_playback->a_progress.update(qMin(dt, 1.), anim::linear); + } + if (timer) Ui::repaintHistoryItem(_parent); + } + } +} + +void HistoryDocument::attachToItem(HistoryItem *item) { App::regDocumentItem(_data, item); } -void HistoryDocument::unregItem(HistoryItem *item) { +void HistoryDocument::detachFromItem(HistoryItem *item) { App::unregDocumentItem(_data, item); } -void HistoryDocument::updateFrom(const MTPMessageMedia &media, HistoryItem *parent, bool allowEmitResize) { +void HistoryDocument::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { if (media.type() == mtpc_messageMediaDocument) { App::feedDocument(media.c_messageMediaDocument().vdocument, _data); + if (!_data->data().isEmpty()) { + if (_data->voice()) { + Local::writeAudio(_data->mediaKey(), _data->data()); + } else { + Local::writeStickerImage(_data->mediaKey(), _data->data()); + } + } } } @@ -4358,12 +4782,13 @@ ImagePtr HistoryDocument::replyPreview() { } HistoryGif::HistoryGif(DocumentData *document, const QString &caption, const HistoryItem *parent) : HistoryFileMedia() +, _parent(nullptr) , _data(document) , _thumbw(1) , _thumbh(1) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) -, _gif(0) { - setLinks(new GifOpenLink(_data), new GifOpenLink(_data), new DocumentCancelLink(_data)); +, _gif(nullptr) { + setDocumentLinks(_data, true); setStatusSize(FileStatusSizeReady); @@ -4375,12 +4800,13 @@ HistoryGif::HistoryGif(DocumentData *document, const QString &caption, const His } HistoryGif::HistoryGif(const HistoryGif &other) : HistoryFileMedia() -, _parent(0) +, _parent(nullptr) , _data(other._data) , _thumbw(other._thumbw) , _thumbh(other._thumbh) -, _gif(0) { - setLinks(new GifOpenLink(_data), new GifOpenLink(_data), new DocumentCancelLink(_data)); +, _caption(other._caption) +, _gif(nullptr) { + setDocumentLinks(_data, true); setStatusSize(other._statusSize); } @@ -4427,8 +4853,8 @@ void HistoryGif::initDimensions(const HistoryItem *parent) { _thumbh = th; _maxw = qMax(tw, int32(st::minPhotoSize)); _minh = qMax(th, int32(st::minPhotoSize)); + _maxw = qMax(_maxw, parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); if (!gif() || !_gif->ready()) { - _maxw = qMax(_maxw, parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); _maxw = qMax(_maxw, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); } if (bubble) { @@ -4440,10 +4866,10 @@ void HistoryGif::initDimensions(const HistoryItem *parent) { } } -int32 HistoryGif::resize(int32 width, const HistoryItem *parent) { +int HistoryGif::resizeGetHeight(int width, const HistoryItem *parent) { bool bubble = parent->hasBubble(); - int32 tw = 0, th = 0; + int tw = 0, th = 0; if (gif() && _gif->ready()) { tw = convertScale(_gif->width()); th = convertScale(_gif->height()); @@ -4478,12 +4904,12 @@ int32 HistoryGif::resize(int32 width, const HistoryItem *parent) { _width = qMax(tw, int32(st::minPhotoSize)); _height = qMax(th, int32(st::minPhotoSize)); + _width = qMax(_width, parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); if (gif() && _gif->ready()) { if (!_gif->started()) { _gif->start(_thumbw, _thumbh, _width, _height, true); } } else { - _width = qMax(_width, parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); _width = qMax(_width, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); } if (bubble) { @@ -4503,13 +4929,12 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo _data->automaticLoad(parent); bool loaded = _data->loaded(), displayLoading = (parent->id < 0) || _data->displayLoading(); if (loaded && !gif() && _gif != BadClipReader && cAutoPlayGif()) { - const_cast(this)->playInline(const_cast(parent)); - if (gif()) _gif->setAutoplay(); + Ui::autoplayMediaInlineAsync(parent->fullId()); } int32 skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = parent->hasBubble(); - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); @@ -4561,7 +4986,7 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); p.setBrush(st::black); } else { - bool over = textlnkDrawOver(_data->loading() ? _cancell : _savel); + bool over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); } p.setOpacity(radialOpacity * p.opacity()); @@ -4575,7 +5000,7 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo if (_data->loaded() && !radial) { icon = (selected ? st::msgFileInPlaySelected : st::msgFileInPlay); } else if (radial || _data->loading()) { - if (parent->id > 0) { + if (parent->id > 0 || _data->uploading()) { icon = (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); } } else { @@ -4598,22 +5023,19 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo p.setFont(st::normalFont); p.setPen(st::white); p.drawTextLeft(statusX, statusY, _width, _statusText, statusW - 2 * st::msgDateImgPadding.x()); - - // date - if (_caption.isEmpty() && parent->getMedia() == this) { - int32 fullRight = skipx + width, fullBottom = skipy + height; - parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); - } } } if (!_caption.isEmpty()) { p.setPen(st::black); _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw); + } else if (parent->getMedia() == this && (_data->uploading() || App::hoveredItem() == parent)) { + int32 fullRight = skipx + width, fullBottom = skipy + height; + parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); } } -void HistoryGif::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { +void HistoryGif::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int x, int y, const HistoryItem *parent) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = parent->hasBubble(); @@ -4683,15 +5105,15 @@ void HistoryGif::updateStatusText(const HistoryItem *parent) const { } } -void HistoryGif::regItem(HistoryItem *item) { +void HistoryGif::attachToItem(HistoryItem *item) { App::regDocumentItem(_data, item); } -void HistoryGif::unregItem(HistoryItem *item) { +void HistoryGif::detachFromItem(HistoryItem *item) { App::unregDocumentItem(_data, item); } -void HistoryGif::updateFrom(const MTPMessageMedia &media, HistoryItem *parent, bool allowEmitResize) { +void HistoryGif::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { if (media.type() == mtpc_messageMediaDocument) { App::feedDocument(media.c_messageMediaDocument().vdocument, _data); } @@ -4701,7 +5123,7 @@ ImagePtr HistoryGif::replyPreview() { return _data->makeReplyPreview(); } -bool HistoryGif::playInline(HistoryItem *parent) { +bool HistoryGif::playInline(HistoryItem *parent, bool autoplay) { if (gif()) { stopInline(parent); } else { @@ -4710,6 +5132,7 @@ bool HistoryGif::playInline(HistoryItem *parent) { } _gif = new ClipReader(_data->location(), _data->data(), func(parent, &HistoryItem::clipCallback)); App::regGifItem(_gif, parent); + if (gif()) _gif->setAutoplay(); } return true; } @@ -4721,8 +5144,7 @@ void HistoryGif::stopInline(HistoryItem *parent) { _gif = 0; } - parent->initDimensions(); - Notify::historyItemResized(parent); + parent->setPendingInitDimensions(); Notify::historyItemLayoutChanged(parent); } @@ -4745,21 +5167,48 @@ bool HistoryGif::dataLoaded() const { return (!_parent || _parent->id > 0) ? _data->loaded() : false; } +namespace { + +class StickerClickHandler : public LeftButtonClickHandler { +public: + StickerClickHandler(const HistoryItem *item) : _item(item) { + } + +protected: + void onClickImpl() const override { + if (HistoryMedia *media = _item->getMedia()) { + if (DocumentData *document = media->getDocument()) { + if (StickerData *sticker = document->sticker()) { + if (sticker->set.type() != mtpc_inputStickerSetEmpty && App::main()) { + App::main()->stickersBox(sticker->set); + } + } + } + } + } + +private: + const HistoryItem *_item; + +}; + +} // namespace + HistorySticker::HistorySticker(DocumentData *document) : HistoryMedia() , _pixw(1) , _pixh(1) -, _data(document) { +, _data(document) +, _emoji(_data->sticker()->alt) { _data->thumb->load(); - if (!_data->sticker()->alt.isEmpty()) { - _emoji = _data->sticker()->alt; - int32 elen = 0; - if (EmojiPtr e = emojiFromText(_emoji.constData(), _emoji.constEnd(), elen)) { - _emoji = emojiString(e); - } + if (EmojiPtr e = emojiFromText(_emoji)) { + _emoji = emojiString(e); } } void HistorySticker::initDimensions(const HistoryItem *parent) { + if (!_packLink && _data->sticker() && _data->sticker()->set.type() != mtpc_inputStickerSetEmpty) { + _packLink = ClickHandlerPtr(new StickerClickHandler(parent)); + } _pixw = _data->dimensions.width(); _pixh = _data->dimensions.height(); if (_pixw > st::maxStickerSize) { @@ -4774,18 +5223,27 @@ void HistorySticker::initDimensions(const HistoryItem *parent) { if (_pixh < 1) _pixh = 1; _maxw = qMax(_pixw, int16(st::minPhotoSize)); _minh = qMax(_pixh, int16(st::minPhotoSize)); - if (const HistoryReply *reply = toHistoryReply(parent)) { - _maxw += st::msgReplyPadding.left() + reply->replyToWidth(); + if (parent->getMedia() == this) { + _maxw += additionalWidth(parent); } _height = _minh; } -int32 HistorySticker::resize(int32 width, const HistoryItem *parent) { // return new height +int HistorySticker::resizeGetHeight(int width, const HistoryItem *parent) { // return new height _width = qMin(width, _maxw); - if (const HistoryReply *reply = toHistoryReply(parent)) { - int32 usew = _maxw - st::msgReplyPadding.left() - reply->replyToWidth(); - int32 rw = _width - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.right(); - reply->resizeVia(rw); + if (parent->getMedia() == this) { + auto *via = parent->Get(); + auto *reply = parent->Get(); + if (via || reply) { + int usew = _maxw - additionalWidth(via, reply); + int availw = _width - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left(); + if (via) { + via->resize(availw); + } + if (reply) { + reply->resize(availw); + } + } } return _height; } @@ -4796,13 +5254,14 @@ void HistorySticker::draw(Painter &p, const HistoryItem *parent, const QRect &r, _data->checkSticker(); bool loaded = _data->loaded(); - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel, hovered, pressed; + bool out = parent->out(), isPost = parent->isPost(), childmedia = (parent->getMedia() != this); - int32 usew = _maxw, usex = 0; - const HistoryReply *reply = toHistoryReply(parent); - if (reply) { - usew -= st::msgReplyPadding.left() + reply->replyToWidth(); - if (fromChannel) { + int usew = _maxw, usex = 0; + auto *via = childmedia ? nullptr : parent->Get(); + auto *reply = childmedia ? nullptr : parent->Get(); + if (via || reply) { + usew -= additionalWidth(via, reply); + if (isPost) { } else if (out) { usex = _width - usew; } @@ -4823,43 +5282,92 @@ void HistorySticker::draw(Painter &p, const HistoryItem *parent, const QRect &r, } } - if (parent->getMedia() == this) { - parent->drawInfo(p, usex + usew, _height, usex * 2 + usew, selected, InfoDisplayOverImage); + if (!childmedia) { + parent->drawInfo(p, usex + usew, _height, usex * 2 + usew, selected, InfoDisplayOverBackground); - if (reply) { - int32 rw = _width - usew - st::msgReplyPadding.left(), rh = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - int32 rx = fromChannel ? (usew + st::msgReplyPadding.left()) : (out ? 0 : (usew + st::msgReplyPadding.left())), ry = _height - rh; - if (rtl()) rx = _width - rx - rw; + if (via || reply) { + int rectw = _width - usew - st::msgReplyPadding.left(); + int recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); + if (via) { + recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); + } + if (reply) { + recth += st::msgReplyBarSize.height(); + } + int rectx = isPost ? (usew + st::msgReplyPadding.left()) : (out ? 0 : (usew + st::msgReplyPadding.left())); + int recty = _height - recth; + if (rtl()) rectx = _width - rectx - rectw; - App::roundRect(p, rx, ry, rw, rh, selected ? App::msgServiceSelectBg() : App::msgServiceBg(), selected ? ServiceSelectedCorners : ServiceCorners); + // Make the bottom of the rect at the same level as the bottom of the info rect. + recty -= st::msgDateImgDelta; - reply->drawReplyTo(p, rx + st::msgReplyPadding.left(), ry, rw - st::msgReplyPadding.left() - st::msgReplyPadding.right(), selected, true); + App::roundRect(p, rectx, recty, rectw, recth, selected ? App::msgServiceSelectBg() : App::msgServiceBg(), selected ? ServiceSelectedCorners : ServiceCorners); + rectx += st::msgReplyPadding.left(); + rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right(); + if (via) { + p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->_text); + int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); + recty += skip; + } + if (reply) { + HistoryMessageReply::PaintFlags flags = 0; + if (selected) { + flags |= HistoryMessageReply::PaintSelected; + } + reply->paint(p, parent, rectx, recty, rectw, flags); + } } } } -void HistorySticker::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { +void HistorySticker::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + bool out = parent->out(), isPost = parent->isPost(), childmedia = (parent->getMedia() != this); - int32 usew = _maxw, usex = 0; - const HistoryReply *reply = toHistoryReply(parent); - if (reply) { - usew -= reply->replyToWidth(); - if (fromChannel) { + int usew = _maxw, usex = 0; + auto *via = childmedia ? nullptr : parent->Get(); + auto *reply = childmedia ? nullptr : parent->Get(); + if (via || reply) { + usew -= additionalWidth(via, reply); + if (isPost) { } else if (out) { usex = _width - usew; } } if (rtl()) usex = _width - usex - usew; - if (reply) { - int32 rw = _width - usew, rh = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - int32 rx = fromChannel ? (usew + st::msgReplyPadding.left()) : (out ? 0 : (usew + st::msgReplyPadding.left())), ry = _height - rh; - if (rtl()) rx = _width - rx - rw; - if (x >= rx && y >= ry && x < rx + rw && y < ry + rh) { - lnk = reply->replyToLink(); - return; + + if (via || reply) { + int rectw = _width - usew - st::msgReplyPadding.left(); + int recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); + if (via) { + recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); + } + if (reply) { + recth += st::msgReplyBarSize.height(); + } + int rectx = isPost ? (usew + st::msgReplyPadding.left()) : (out ? 0 : (usew + st::msgReplyPadding.left())); + int recty = _height - recth; + if (rtl()) rectx = _width - rectx - rectw; + + // Make the bottom of the rect at the same level as the bottom of the info rect. + recty -= st::msgDateImgDelta; + + if (via) { + int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom()); + if (x >= rectx && y >= recty && x < rectx + rectw && y < recty + viah) { + lnk = via->_lnk; + return; + } + int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0); + recty += skip; + recth -= skip; + } + if (reply) { + if (x >= rectx && y >= recty && x < rectx + rectw && y < recty + recth) { + lnk = reply->replyToLink(); + return; + } } } if (parent->getMedia() == this) { @@ -4868,6 +5376,12 @@ void HistorySticker::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 state = HistoryInDateCursorState; } } + + int pixLeft = usex + (usew - _pixw) / 2, pixTop = (_minh - _pixh) / 2; + if (x >= pixLeft && x < pixLeft + _pixw && y >= pixTop && y < pixTop + _pixh) { + lnk = _packLink; + return; + } } const QString HistorySticker::inDialogsText() const { @@ -4878,39 +5392,46 @@ const QString HistorySticker::inHistoryText() const { return qsl("[ ") + inDialogsText() + qsl(" ]"); } -void HistorySticker::regItem(HistoryItem *item) { +void HistorySticker::attachToItem(HistoryItem *item) { App::regDocumentItem(_data, item); } -void HistorySticker::unregItem(HistoryItem *item) { +void HistorySticker::detachFromItem(HistoryItem *item) { App::unregDocumentItem(_data, item); } -void HistorySticker::updateFrom(const MTPMessageMedia &media, HistoryItem *parent, bool allowEmitResize) { +void HistorySticker::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { if (media.type() == mtpc_messageMediaDocument) { App::feedDocument(media.c_messageMediaDocument().vdocument, _data); if (!_data->data().isEmpty()) { - Local::writeStickerImage(mediaKey(DocumentFileLocation, _data->dc, _data->id), _data->data()); + Local::writeStickerImage(_data->mediaKey(), _data->data()); } } } -void SendMessageLink::onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton) { - Ui::showPeerHistory(peer()->id, ShowAtUnreadMsgId); +int HistorySticker::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const { + int result = 0; + if (via) { + accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->_maxWidth + st::msgReplyPadding.left()); } + if (reply) { + accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); + } + return result; } -void AddContactLink::onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton) { - if (HistoryItem *item = App::histItemById(peerToChannel(peer()), msgid())) { - if (HistoryMedia *media = item->getMedia()) { - if (media->type() == MediaTypeContact) { - QString fname = static_cast(media)->fname(); - QString lname = static_cast(media)->lname(); - QString phone = static_cast(media)->phone(); - Ui::showLayer(new AddContactBox(fname, lname, phone)); - } +void SendMessageClickHandler::onClickImpl() const { + Ui::showPeerHistory(peer()->id, ShowAtUnreadMsgId); +} + +void AddContactClickHandler::onClickImpl() const { + if (HistoryItem *item = App::histItemById(peerToChannel(peer()), msgid())) { + if (HistoryMedia *media = item->getMedia()) { + if (media->type() == MediaTypeContact) { + QString fname = static_cast(media)->fname(); + QString lname = static_cast(media)->lname(); + QString phone = static_cast(media)->phone(); + Ui::showLayer(new AddContactBox(fname, lname, phone)); } } } @@ -4934,13 +5455,13 @@ void HistoryContact::initDimensions(const HistoryItem *parent) { _contact = _userId ? App::userLoaded(_userId) : 0; if (_contact) { - _contact->photo->load(); + _contact->loadUserpic(); } if (_contact && _contact->contact > 0) { - _linkl.reset(new SendMessageLink(_contact)); + _linkl.reset(new SendMessageClickHandler(_contact)); _link = lang(lng_profile_send_message).toUpper(); } else if (_userId) { - _linkl.reset(new AddContactLink(parent->history()->peer->id, parent->id)); + _linkl.reset(new AddContactClickHandler(parent->history()->peer->id, parent->id)); _link = lang(lng_profile_add_contact).toUpper(); } _linkw = _link.isEmpty() ? 0 : st::semiboldFont->width(_link); @@ -4971,7 +5492,7 @@ void HistoryContact::draw(Painter &p, const HistoryItem *parent, const QRect &r, if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; if (width >= _maxw) { width = _maxw; @@ -4986,17 +5507,16 @@ void HistoryContact::draw(Painter &p, const HistoryItem *parent, const QRect &r, linktop = st::msgFileThumbLinkTop; QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, width)); - if (_contact && _contact->photo->loaded()) { - QPixmap thumb = _contact->photo->pixRounded(st::msgFileThumbSize, st::msgFileThumbSize); - p.drawPixmap(rthumb.topLeft(), thumb); + if (_contact) { + _contact->paintUserpic(p, st::msgFileThumbSize, rthumb.x(), rthumb.y()); } else { - p.drawPixmap(rthumb.topLeft(), userDefPhoto(_contact ? _contact->colorIndex : (qAbs(_userId) % UserColorsCount))->pixRounded(st::msgFileThumbSize, st::msgFileThumbSize)); + p.drawPixmap(rthumb.topLeft(), userDefPhoto(qAbs(_userId) % UserColorsCount)->pixCircled(st::msgFileThumbSize, st::msgFileThumbSize)); } if (selected) { App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); } - bool over = textlnkDrawOver(_linkl); + bool over = ClickHandler::showAsActive(_linkl); p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont); p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg)); p.drawTextLeft(nameleft, linktop, width, _link, _linkw); @@ -5007,7 +5527,7 @@ void HistoryContact::draw(Painter &p, const HistoryItem *parent, const QRect &r, statustop = st::msgFileStatusTop; QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, width)); - p.drawPixmap(inner.topLeft(), userDefPhoto(qAbs(parent->id) % UserColorsCount)->pixRounded(st::msgFileSize, st::msgFileSize)); + p.drawPixmap(inner.topLeft(), userDefPhoto(qAbs(parent->id) % UserColorsCount)->pixCircled(st::msgFileSize, st::msgFileSize)); } int32 namewidth = width - nameleft - nameright; @@ -5021,8 +5541,8 @@ void HistoryContact::draw(Painter &p, const HistoryItem *parent, const QRect &r, p.drawTextLeft(nameleft, statustop, width, _phone); } -void HistoryContact::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; +void HistoryContact::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { + bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; if (_userId) { @@ -5034,7 +5554,7 @@ void HistoryContact::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 } } if (x >= 0 && y >= 0 && x < _width && y < _height && _contact) { - lnk = _contact->lnk; + lnk = _contact->openLink(); return; } } @@ -5047,26 +5567,24 @@ const QString HistoryContact::inHistoryText() const { return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" : ") + _name.original() + qsl(", ") + _phone + qsl(" ]"); } -void HistoryContact::regItem(HistoryItem *item) { +void HistoryContact::attachToItem(HistoryItem *item) { if (_userId) { App::regSharedContactItem(_userId, item); } } -void HistoryContact::unregItem(HistoryItem *item) { +void HistoryContact::detachFromItem(HistoryItem *item) { if (_userId) { App::unregSharedContactItem(_userId, item); } } -void HistoryContact::updateFrom(const MTPMessageMedia &media, HistoryItem *parent, bool allowEmitResize) { +void HistoryContact::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { if (media.type() == mtpc_messageMediaContact) { if (_userId != media.c_messageMediaContact().vuser_id.v) { - unregItem(parent); + detachFromItem(parent); _userId = media.c_messageMediaContact().vuser_id.v; - regItem(parent); - parent->initDimensions(); - if (allowEmitResize) Notify::historyItemResized(parent); + attachToItem(parent); } } } @@ -5130,10 +5648,9 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { _maxw = _minh = _height = 0; return; } - if (!_lineHeight) _lineHeight = qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height); - if (!_openl && !_data->url.isEmpty()) _openl = TextLinkPtr(new TextLink(_data->url)); + if (!_openl && !_data->url.isEmpty()) _openl.reset(new UrlClickHandler(_data->url, true)); // init layout QString title(_data->title.isEmpty() ? _data->author : _data->title); @@ -5148,7 +5665,7 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { } else { _asArticle = true; } - if (_asArticle && (_data->description.isEmpty() || (title.isEmpty() && _data->siteName.isEmpty()))) { + if (_asArticle && _data->description.isEmpty() && title.isEmpty() && _data->siteName.isEmpty()) { _asArticle = false; } } else { @@ -5162,6 +5679,8 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { _attach = new HistorySticker(_data->doc); } else if (_data->doc->isAnimation()) { _attach = new HistoryGif(_data->doc, QString(), parent); + } else if (_data->doc->isVideo()) { + _attach = new HistoryVideo(_data->doc, QString(), parent); } else { _attach = new HistoryDocument(_data->doc, QString(), parent); } @@ -5253,12 +5772,12 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { _maxw += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); _minh += st::msgPadding.bottom(); if (_asArticle) { - _minh = resize(_maxw, parent); // hack + _minh = resizeGetHeight(_maxw, parent); // hack // _minh += st::msgDateFont->height; } } -int32 HistoryWebPage::resize(int32 width, const HistoryItem *parent) { +int HistoryWebPage::resizeGetHeight(int width, const HistoryItem *parent) { if (_data->pendingTill) { _width = width; _height = _minh; @@ -5335,7 +5854,7 @@ int32 HistoryWebPage::resize(int32 width, const HistoryItem *parent) { QMargins bubble(_attach->bubbleMargins()); - _attach->resize(width + bubble.left() + bubble.right(), parent); + _attach->resizeGetHeight(width + bubble.left() + bubble.right(), parent); _height += _attach->height() - bubble.top() - bubble.bottom(); if (_attach->customInfoLayout() && _attach->currentWidth() + parent->skipBlockWidth() > width + bubble.left() + bubble.right()) { _height += st::msgDateFont->height; @@ -5351,7 +5870,7 @@ void HistoryWebPage::draw(Painter &p, const HistoryItem *parent, const QRect &r, if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; style::color barfg = (selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor)); style::color semibold = (selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); @@ -5451,7 +5970,7 @@ void HistoryWebPage::draw(Painter &p, const HistoryItem *parent, const QRect &r, } } -void HistoryWebPage::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { +void HistoryWebPage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; @@ -5506,26 +6025,26 @@ void HistoryWebPage::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 } } -void HistoryWebPage::linkOver(HistoryItem *parent, const TextLinkPtr &lnk) { +void HistoryWebPage::clickHandlerActiveChanged(HistoryItem *parent, const ClickHandlerPtr &p, bool active) { if (_attach) { - _attach->linkOver(parent, lnk); + _attach->clickHandlerActiveChanged(parent, p, active); } } -void HistoryWebPage::linkOut(HistoryItem *parent, const TextLinkPtr &lnk) { +void HistoryWebPage::clickHandlerPressedChanged(HistoryItem *parent, const ClickHandlerPtr &p, bool pressed) { if (_attach) { - _attach->linkOut(parent, lnk); + _attach->clickHandlerPressedChanged(parent, p, pressed); } } -void HistoryWebPage::regItem(HistoryItem *item) { +void HistoryWebPage::attachToItem(HistoryItem *item) { App::regWebPageItem(_data, item); - if (_attach) _attach->regItem(item); + if (_attach) _attach->attachToItem(item); } -void HistoryWebPage::unregItem(HistoryItem *item) { +void HistoryWebPage::detachFromItem(HistoryItem *item) { App::unregWebPageItem(_data, item); - if (_attach) _attach->unregItem(item); + if (_attach) _attach->detachFromItem(item); } const QString HistoryWebPage::inDialogsText() const { @@ -5545,10 +6064,10 @@ HistoryWebPage::~HistoryWebPage() { } namespace { - ImageLinkManager manager; + LocationManager manager; } -void ImageLinkManager::init() { +void LocationManager::init() { if (manager) delete manager; manager = new QNetworkAccessManager(); App::setProxySettings(*manager); @@ -5568,11 +6087,11 @@ void ImageLinkManager::init() { black = new ImagePtr(p, "PNG"); } -void ImageLinkManager::reinit() { +void LocationManager::reinit() { if (manager) App::setProxySettings(*manager); } -void ImageLinkManager::deinit() { +void LocationManager::deinit() { if (manager) { delete manager; manager = 0; @@ -5597,33 +6116,27 @@ void deinitImageLinkManager() { manager.deinit(); } -void ImageLinkManager::getData(ImageLinkData *data) { +void LocationManager::getData(LocationData *data) { if (!manager) { DEBUG_LOG(("App Error: getting image link data without manager init!")); return failed(data); } - QString url; - switch (data->type) { - case GoogleMapsLink: { - int32 w = st::locationSize.width(), h = st::locationSize.height(); - int32 zoom = 13, scale = 1; - if (cScale() == dbisTwo || cRetina()) { - scale = 2; - } else { - w = convertScale(w); - h = convertScale(h); - } - url = qsl("https://maps.googleapis.com/maps/api/staticmap?center=") + data->id.mid(9) + qsl("&zoom=%1&size=%2x%3&maptype=roadmap&scale=%4&markers=color:red|size:big|").arg(zoom).arg(w).arg(h).arg(scale) + data->id.mid(9) + qsl("&sensor=false"); - QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url))); - imageLoadings[reply] = data; - } break; - default: { - failed(data); - } break; + + int32 w = st::locationSize.width(), h = st::locationSize.height(); + int32 zoom = 13, scale = 1; + if (cScale() == dbisTwo || cRetina()) { + scale = 2; + } else { + w = convertScale(w); + h = convertScale(h); } + QString coords = qsl("%1,%2").arg(data->coords.lat).arg(data->coords.lon); + QString url = qsl("https://maps.googleapis.com/maps/api/staticmap?center=") + coords + qsl("&zoom=%1&size=%2x%3&maptype=roadmap&scale=%4&markers=color:red|size:big|").arg(zoom).arg(w).arg(h).arg(scale) + coords + qsl("&sensor=false"); + QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url))); + imageLoadings[reply] = data; } -void ImageLinkManager::onFinished(QNetworkReply *reply) { +void LocationManager::onFinished(QNetworkReply *reply) { if (!manager) return; if (reply->error() != QNetworkReply::NoError) return onFailed(reply); @@ -5633,9 +6146,9 @@ void ImageLinkManager::onFinished(QNetworkReply *reply) { if (status == 301 || status == 302) { QString loc = reply->header(QNetworkRequest::LocationHeader).toString(); if (!loc.isEmpty()) { - QMap::iterator i = dataLoadings.find(reply); + QMap::iterator i = dataLoadings.find(reply); if (i != dataLoadings.cend()) { - ImageLinkData *d = i.value(); + LocationData *d = i.value(); if (serverRedirects.constFind(d) == serverRedirects.cend()) { serverRedirects.insert(d, 1); } else if (++serverRedirects[d] > MaxHttpRedirects) { @@ -5646,7 +6159,7 @@ void ImageLinkManager::onFinished(QNetworkReply *reply) { dataLoadings.insert(manager->get(QNetworkRequest(loc)), d); return; } else if ((i = imageLoadings.find(reply)) != imageLoadings.cend()) { - ImageLinkData *d = i.value(); + LocationData *d = i.value(); if (serverRedirects.constFind(d) == serverRedirects.cend()) { serverRedirects.insert(d, 1); } else if (++serverRedirects[d] > MaxHttpRedirects) { @@ -5665,8 +6178,8 @@ void ImageLinkManager::onFinished(QNetworkReply *reply) { } } - ImageLinkData *d = 0; - QMap::iterator i = dataLoadings.find(reply); + LocationData *d = 0; + QMap::iterator i = dataLoadings.find(reply); if (i != dataLoadings.cend()) { d = i.value(); dataLoadings.erase(i); @@ -5677,9 +6190,7 @@ void ImageLinkManager::onFinished(QNetworkReply *reply) { DEBUG_LOG(("JSON Error: Bad json received in onFinished() for image link")); return onFailed(reply); } - switch (d->type) { - case GoogleMapsLink: failed(d); break; - } + failed(d); if (App::main()) App::main()->update(); } else { @@ -5710,11 +6221,11 @@ void ImageLinkManager::onFinished(QNetworkReply *reply) { } } -void ImageLinkManager::onFailed(QNetworkReply *reply) { +void LocationManager::onFailed(QNetworkReply *reply) { if (!manager) return; - ImageLinkData *d = 0; - QMap::iterator i = dataLoadings.find(reply); + LocationData *d = 0; + QMap::iterator i = dataLoadings.find(reply); if (i != dataLoadings.cend()) { d = i.value(); dataLoadings.erase(i); @@ -5725,19 +6236,19 @@ void ImageLinkManager::onFailed(QNetworkReply *reply) { imageLoadings.erase(i); } } - DEBUG_LOG(("Network Error: failed to get data for image link %1, error %2").arg(d ? d->id : 0).arg(reply->errorString())); + DEBUG_LOG(("Network Error: failed to get data for image link %1,%2 error %3").arg(d ? d->coords.lat : 0).arg(d ? d->coords.lon : 0).arg(reply->errorString())); if (d) { failed(d); } } -void ImageLinkManager::failed(ImageLinkData *data) { +void LocationManager::failed(LocationData *data) { data->loading = false; data->thumb = *black; serverRedirects.remove(data); } -void ImageLinkData::load() { +void LocationData::load() { if (!thumb->isNull()) return thumb->load(false, false); if (loading) return; @@ -5745,9 +6256,9 @@ void ImageLinkData::load() { manager.getData(this); } -HistoryImageLink::HistoryImageLink(const QString &url, const QString &title, const QString &description) : HistoryMedia(), -_title(st::msgMinWidth), -_description(st::msgMinWidth) { +HistoryLocation::HistoryLocation(const LocationCoords &coords, const QString &title, const QString &description) : HistoryMedia() +, _title(st::msgMinWidth) +, _description(st::msgMinWidth) { if (!title.isEmpty()) { _title.setText(st::webPageTitleFont, textClean(title), _webpageTitleOptions); } @@ -5755,17 +6266,11 @@ _description(st::msgMinWidth) { _description.setText(st::webPageDescriptionFont, textClean(description), _webpageDescriptionOptions); } - if (url.startsWith(qsl("location:"))) { - QString lnk = qsl("https://maps.google.com/maps?q=") + url.mid(9) + qsl("&ll=") + url.mid(9) + qsl("&z=17"); - _link.reset(new TextLink(lnk)); - - _data = App::imageLinkSet(url, GoogleMapsLink, lnk); - } else { - _link.reset(new TextLink(url)); - } + _link.reset(new LocationClickHandler(coords)); + _data = App::location(coords); } -void HistoryImageLink::initDimensions(const HistoryItem *parent) { +void HistoryLocation::initDimensions(const HistoryItem *parent) { bool bubble = parent->hasBubble(); int32 tw = fullWidth(), th = fullHeight(); @@ -5789,14 +6294,14 @@ void HistoryImageLink::initDimensions(const HistoryItem *parent) { _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_title.isEmpty() || !_description.isEmpty()) { _minh += st::webPagePhotoSkip; - if (!parent->toHistoryForwarded() && !parent->toHistoryReply()) { + if (!parent->Has() && !parent->Has()) { _minh += st::msgPadding.top(); } } } } -int32 HistoryImageLink::resize(int32 width, const HistoryItem *parent) { +int32 HistoryLocation::resize(int32 width, const HistoryItem *parent) { bool bubble = parent->hasBubble(); _width = qMin(width, _maxw); @@ -5829,7 +6334,7 @@ int32 HistoryImageLink::resize(int32 width, const HistoryItem *parent) { } if (!_title.isEmpty() || !_description.isEmpty()) { _height += st::webPagePhotoSkip; - if (!parent->toHistoryForwarded() && !parent->toHistoryReply()) { + if (!parent->Has() && !parent->Has()) { _height += st::msgPadding.top(); } } @@ -5837,18 +6342,18 @@ int32 HistoryImageLink::resize(int32 width, const HistoryItem *parent) { return _height; } -void HistoryImageLink::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { +void HistoryLocation::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = parent->hasBubble(); - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; if (bubble) { skipx = st::mediaPadding.left(); skipy = st::mediaPadding.top(); if (!_title.isEmpty() || !_description.isEmpty()) { - if (!parent->toHistoryForwarded() && !parent->toHistoryReply()) { + if (!parent->Has() && !parent->Has()) { skipy += st::msgPadding.top(); } } @@ -5889,7 +6394,7 @@ void HistoryImageLink::draw(Painter &p, const HistoryItem *parent, const QRect & } p.drawPixmap(QPoint(skipx, skipy), pix); } else { - App::roundRect(p, skipx, skipy, width, height, st::black, BlackCorners); + App::roundRect(p, skipx, skipy, width, height, st::white, MessageInCorners); } if (selected) { App::roundRect(p, skipx, skipy, width, height, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); @@ -5901,7 +6406,7 @@ void HistoryImageLink::draw(Painter &p, const HistoryItem *parent, const QRect & } } -void HistoryImageLink::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { +void HistoryLocation::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; bool bubble = parent->hasBubble(); @@ -5911,7 +6416,7 @@ void HistoryImageLink::getState(TextLinkPtr &lnk, HistoryCursorState &state, int skipy = st::mediaPadding.top(); if (!_title.isEmpty() || !_description.isEmpty()) { - if (!parent->toHistoryForwarded() && !parent->toHistoryReply()) { + if (!parent->Has() && !parent->Has()) { skipy += st::msgPadding.top(); } } @@ -5943,134 +6448,417 @@ void HistoryImageLink::getState(TextLinkPtr &lnk, HistoryCursorState &state, int } } -const QString HistoryImageLink::inDialogsText() const { - if (_data) { - switch (_data->type) { - case GoogleMapsLink: return lang(lng_maps_point); - } - } - return QString(); +const QString HistoryLocation::inDialogsText() const { + return lang(lng_maps_point); } -const QString HistoryImageLink::inHistoryText() const { - if (_data) { - switch (_data->type) { - case GoogleMapsLink: return qsl("[ ") + lang(lng_maps_point) + qsl(" : ") + _link->text() + qsl(" ]"); - } - } - return qsl("[ Link : ") + _link->text() + qsl(" ]"); +const QString HistoryLocation::inHistoryText() const { + return qsl("[ ") + lang(lng_maps_point) + qsl(" : ") + _link->text() + qsl(" ]"); } -int32 HistoryImageLink::fullWidth() const { - if (_data) { - switch (_data->type) { - case GoogleMapsLink: return st::locationSize.width(); - } - } - return st::minPhotoSize; +int32 HistoryLocation::fullWidth() const { + return st::locationSize.width(); } -int32 HistoryImageLink::fullHeight() const { - if (_data) { - switch (_data->type) { - case GoogleMapsLink: return st::locationSize.height(); - } - } - return st::minPhotoSize; +int32 HistoryLocation::fullHeight() const { + return st::locationSize.height(); } -void ViaInlineBotLink::onClick(Qt::MouseButton button) const { +void ViaInlineBotClickHandler::onClickImpl() const { App::insertBotCommand('@' + _bot->username); } -HistoryMessageVia::HistoryMessageVia(int32 userId) - : bot(App::userLoaded(peerFromUser(userId))) - , width(0) - , maxWidth(st::msgServiceNameFont->width(qsl("via @") + bot->username)) - , lnk(new ViaInlineBotLink(bot)) { +void HistoryMessageVia::create(int32 userId) { + _bot = App::user(peerFromUser(userId)); + _maxWidth = st::msgServiceNameFont->width(lng_inline_bot_via(lt_inline_bot, '@' + _bot->username)); + _lnk.reset(new ViaInlineBotClickHandler(_bot)); } -bool HistoryMessageVia::isNull() const { - return !bot || bot->username.isEmpty(); -} - -void HistoryMessageVia::resize(int32 availw) { - if (width < maxWidth && availw > width) { - if (availw < maxWidth) { - text = st::msgServiceNameFont->elided(qsl("via @") + bot->username, availw); - width = st::msgServiceNameFont->width(text); - } else { - text = qsl("via @") + bot->username; - width = maxWidth; - } - } else if (availw < width) { - if (availw > 0) { - text = st::msgServiceNameFont->elided(qsl("via @") + bot->username, availw); - width = st::msgServiceNameFont->width(text); - } else { - text = QString(); - width = 0; +void HistoryMessageVia::resize(int32 availw) const { + if (availw < 0) { + _text = QString(); + _width = 0; + } else { + _text = lng_inline_bot_via(lt_inline_bot, '@' + _bot->username); + if (availw < _maxWidth) { + _text = st::msgServiceNameFont->elided(_text, availw); + _width = st::msgServiceNameFont->width(_text); + } else if (_width < _maxWidth) { + _width = _maxWidth; } } } -HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, const MTPDmessage &msg) : - HistoryItem(history, block, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) -, _text(st::msgMinWidth) -, _textWidth(0) -, _textHeight(0) -, _via(msg.has_via_bot_id() ? new HistoryMessageVia(msg.vvia_bot_id.v) : 0) -, _media(0) -, _views(msg.has_views() ? msg.vviews.v : -1) { +void HistoryMessageSigned::create(UserData *from, const QDateTime &date) { + QString time = qsl(", ") + date.toString(cTimeFormat()), name = App::peerName(from); + int32 timew = st::msgDateFont->width(time), namew = st::msgDateFont->width(name); + if (timew + namew > st::maxSignatureSize) { + name = st::msgDateFont->elided(from->firstName, st::maxSignatureSize - timew); + } + _signature.setText(st::msgDateFont, name + time, _textNameOptions); +} + +int HistoryMessageSigned::maxWidth() const { + return _signature.maxWidth(); +} + +void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { + QString text; + if (_authorOriginal != _fromOriginal) { + text = lng_forwarded_signed(lt_channel, App::peerName(_authorOriginal), lt_user, App::peerName(_fromOriginal)); + } else { + text = App::peerName(_authorOriginal); + } + if (via) { + if (_authorOriginal->isChannel()) { + text = lng_forwarded_channel_via(lt_channel, textcmdLink(1, text), lt_inline_bot, textcmdLink(2, '@' + via->_bot->username)); + } else { + text = lng_forwarded_via(lt_user, textcmdLink(1, text), lt_inline_bot, textcmdLink(2, '@' + via->_bot->username)); + } + } else { + if (_authorOriginal->isChannel()) { + text = lng_forwarded_channel(lt_channel, textcmdLink(1, text)); + } else { + text = lng_forwarded(lt_user, textcmdLink(1, text)); + } + } + TextParseOptions opts = { TextParseRichText, 0, 0, Qt::LayoutDirectionAuto }; + textstyleSet(&st::inFwdTextStyle); + _text.setText(st::msgServiceNameFont, text, opts); + textstyleRestore(); + _text.setLink(1, (_originalId && _authorOriginal->isChannel()) ? ClickHandlerPtr(new GoToMessageClickHandler(_authorOriginal->id, _originalId)) : _authorOriginal->openLink()); + if (via) { + _text.setLink(2, via->_lnk); + } +} + +bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) { + if (!force) { + if (replyToMsg || !replyToMsgId) { + return true; + } + } + if (!replyToMsg) { + replyToMsg = App::histItemById(holder->channelId(), replyToMsgId); + if (replyToMsg) { + App::historyRegDependency(holder, replyToMsg); + } + } + + if (replyToMsg) { + replyToText.setText(st::msgFont, replyToMsg->inReplyText(), _textDlgOptions); + + updateName(); + + replyToLnk.reset(new GoToMessageClickHandler(replyToMsg->history()->peer->id, replyToMsg->id)); + if (!replyToMsg->Has()) { + if (UserData *bot = replyToMsg->viaBot()) { + _replyToVia.reset(new HistoryMessageVia()); + _replyToVia->create(peerToUser(bot->id)); + } + } + } else if (force) { + replyToMsgId = 0; + } + if (force) { + holder->setPendingInitDimensions(); + } + return (replyToMsg || !replyToMsgId); +} + +void HistoryMessageReply::clearData(HistoryMessage *holder) { + _replyToVia.clear(); + if (replyToMsg) { + App::historyUnregDependency(holder, replyToMsg); + replyToMsg = nullptr; + } + replyToMsgId = 0; +} + +void HistoryMessageReply::checkNameUpdate() const { + if (replyToMsg && replyToMsg->author()->nameVersion > replyToVersion) { + updateName(); + } +} + +void HistoryMessageReply::updateName() const { + if (replyToMsg) { + QString name = (_replyToVia && replyToMsg->author()->isUser()) ? replyToMsg->author()->asUser()->firstName : App::peerName(replyToMsg->author()); + replyToName.setText(st::msgServiceNameFont, name, _textNameOptions); + replyToVersion = replyToMsg->author()->nameVersion; + bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; + int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; + int32 w = replyToName.maxWidth(); + if (_replyToVia) { + w += st::msgServiceFont->spacew + _replyToVia->_maxWidth; + } + + _maxReplyWidth = previewSkip + qMax(w, qMin(replyToText.maxWidth(), int32(st::maxSignatureSize))); + } else { + _maxReplyWidth = st::msgDateFont->width(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message)); + } + _maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + _maxReplyWidth + st::msgReplyPadding.right(); +} + +void HistoryMessageReply::resize(int width) const { + if (_replyToVia) { + bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; + int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; + _replyToVia->resize(width - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew); + } +} + +void HistoryMessageReply::itemRemoved(HistoryMessage *holder, HistoryItem *removed) { + if (replyToMsg == removed) { + clearData(holder); + holder->setPendingInitDimensions(); + } +} + +void HistoryMessageReply::paint(Painter &p, const HistoryItem *holder, int x, int y, int w, PaintFlags flags) const { + bool selected = (flags & PaintSelected), outbg = holder->hasOutLayout(); + + style::color bar; + if (flags & PaintInBubble) { + bar = ((flags & PaintSelected) ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor)); + } else { + bar = st::white; + } + QRect rbar(rtlrect(x + st::msgReplyBarPos.x(), y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), w + 2 * x)); + p.fillRect(rbar, bar); + + if (w > st::msgReplyBarSkip) { + if (replyToMsg) { + bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; + int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; + + if (hasPreview) { + ImagePtr replyPreview = replyToMsg->getMedia()->replyPreview(); + if (!replyPreview->isNull()) { + QRect to(rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x)); + p.drawPixmap(to.x(), to.y(), replyPreview->pixSingle(replyPreview->width() / cIntRetinaFactor(), replyPreview->height() / cIntRetinaFactor(), to.width(), to.height())); + if (selected) { + App::roundRect(p, to, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + } + } + } + if (w > st::msgReplyBarSkip + previewSkip) { + if (flags & PaintInBubble) { + p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); + } else { + p.setPen(st::white); + } + replyToName.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x); + if (_replyToVia && w > st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew) { + p.setFont(st::msgServiceFont); + p.drawText(x + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, _replyToVia->_text); + } + + HistoryMessage *replyToAsMsg = replyToMsg->toHistoryMessage(); + if (!(flags & PaintInBubble)) { + } else if ((replyToAsMsg && replyToAsMsg->emptyText()) || replyToMsg->serviceMsg()) { + style::color date(outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg)); + p.setPen(date); + } else { + p.setPen(st::msgColor); + } + replyToText.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top() + st::msgServiceNameFont->height, w - st::msgReplyBarSkip - previewSkip, w + 2 * x); + } + } else { + p.setFont(st::msgDateFont); + style::color date(outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg)); + p.setPen((flags & PaintInBubble) ? date : st::white); + p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message), w - st::msgReplyBarSkip)); + } + } +} + +void HistoryMessage::KeyboardStyle::startPaint(Painter &p) const { + p.setPen(st::msgServiceColor); +} + +style::font HistoryMessage::KeyboardStyle::textFont() const { + return st::msgServiceFont; +} + +void HistoryMessage::KeyboardStyle::repaint(const HistoryItem *item) const { + Ui::repaintHistoryItem(item); +} + +void HistoryMessage::KeyboardStyle::paintButtonBg(Painter &p, const QRect &rect, bool down, float64 howMuchOver) const { + App::roundRect(p, rect, App::msgServiceBg(), ServiceCorners); + if (down) { + howMuchOver = 1.; + } + if (howMuchOver > 0) { + float64 o = p.opacity(); + p.setOpacity(o * (howMuchOver * st::msgBotKbOverOpacity)); + App::roundRect(p, rect, st::white, WhiteCorners); + p.setOpacity(o); + } +} + +void HistoryMessage::KeyboardStyle::paintButtonIcon(Painter &p, const QRect &rect, HistoryMessageReplyMarkup::Button::Type type) const { + style::sprite sprite; + switch (type) { + case HistoryMessageReplyMarkup::Button::Url: sprite = st::msgBotKbUrlIcon; break; + case HistoryMessageReplyMarkup::Button::RequestPhone: sprite = st::msgBotKbRequestPhoneIcon; break; + case HistoryMessageReplyMarkup::Button::RequestLocation: sprite = st::msgBotKbRequestLocationIcon; break; + } + if (!sprite.isEmpty()) { + p.drawSprite(rect.x() + rect.width() - sprite.pxWidth() - st::msgBotKbIconPadding, rect.y() + st::msgBotKbIconPadding, sprite); + } +} + +void HistoryMessage::KeyboardStyle::paintButtonLoading(Painter &p, const QRect &rect) const { + style::sprite sprite = st::msgInvSendingImg; + p.drawSprite(rect.x() + rect.width() - sprite.pxWidth() - st::msgBotKbIconPadding, rect.y() + rect.height() - sprite.pxHeight() - st::msgBotKbIconPadding, sprite); +} + +int HistoryMessage::KeyboardStyle::minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const { + int result = 2 * buttonPadding(), iconWidth = 0; + switch (type) { + case HistoryMessageReplyMarkup::Button::Url: iconWidth = st::msgBotKbUrlIcon.pxWidth(); break; + case HistoryMessageReplyMarkup::Button::RequestPhone: iconWidth = st::msgBotKbRequestPhoneIcon.pxWidth(); break; + case HistoryMessageReplyMarkup::Button::RequestLocation: iconWidth = st::msgBotKbRequestLocationIcon.pxWidth(); break; + case HistoryMessageReplyMarkup::Button::Callback: iconWidth = st::msgInvSendingImg.pxWidth(); break; + } + if (iconWidth > 0) { + result = std::min(result, iconWidth + 2 * int(st::msgBotKbIconPadding)); + } + return result; +} + +HistoryMessage::HistoryMessage(History *history, const MTPDmessage &msg) +: HistoryItem(history, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) { + CreateConfig config; + + if (msg.has_fwd_from() && msg.vfwd_from.type() == mtpc_messageFwdHeader) { + const MTPDmessageFwdHeader &f(msg.vfwd_from.c_messageFwdHeader()); + if (f.has_from_id() || f.has_channel_id()) { + config.authorIdOriginal = f.has_channel_id() ? peerFromChannel(f.vchannel_id) : peerFromUser(f.vfrom_id); + config.fromIdOriginal = f.has_from_id() ? peerFromUser(f.vfrom_id) : peerFromChannel(f.vchannel_id); + if (f.has_channel_post()) config.originalId = f.vchannel_post.v; + } + } + if (msg.has_reply_to_msg_id()) config.replyTo = msg.vreply_to_msg_id.v; + if (msg.has_via_bot_id()) config.viaBotId = msg.vvia_bot_id.v; + if (msg.has_views()) config.viewsCount = msg.vviews.v; + if (msg.has_reply_markup()) config.markup = &msg.vreply_markup; + + createComponents(config); + QString text(textClean(qs(msg.vmessage))); - initTime(); initMedia(msg.has_media() ? (&msg.vmedia) : 0, text); setText(text, msg.has_entities() ? entitiesFromMTP(msg.ventities.c_vector().v) : EntitiesInText()); } -HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, QDateTime date, int32 from, const QString &msg, const EntitiesInText &entities, HistoryMedia *fromMedia) : -HistoryItem(history, block, msgId, flags, date, from) -, _text(st::msgMinWidth) -, _textWidth(0) -, _textHeight(0) -, _via(viaBotId ? new HistoryMessageVia(viaBotId) : 0) -, _media(0) -, _views(fromChannel() ? 1 : -1) { - initTime(); - if (fromMedia) { - _media = fromMedia->clone(); - _media->regItem(this); +HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd) +: HistoryItem(history, id, newForwardedFlags(history->peer, from, fwd) | flags, date, from) { + CreateConfig config; + + UserData *fwdViaBot = fwd->viaBot(); + if (fwdViaBot) config.viaBotId = peerToUser(fwdViaBot->id); + int fwdViewsCount = fwd->viewsCount(); + if (fwdViewsCount > 0) { + config.viewsCount = fwdViewsCount; + } else if (isPost()) { + config.viewsCount = 1; } + + createComponents(config); + + if (HistoryMedia *mediaOriginal = fwd->getMedia()) { + _media.reset(this, mediaOriginal->clone()); + } + setText(fwd->originalText(), fwd->originalEntities()); +} + +HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const QString &msg, const EntitiesInText &entities) +: HistoryItem(history, id, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { + createComponentsHelper(flags, replyTo, viaBotId, MTPnullMarkup); + setText(msg, entities); } -HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption) : -HistoryItem(history, block, msgId, flags, date, from) -, _text(st::msgMinWidth) -, _textWidth(0) -, _textHeight(0) -, _via(viaBotId ? new HistoryMessageVia(viaBotId) : 0) -, _media(0) -, _views(fromChannel() ? 1 : -1) { - initTime(); +HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) +: HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { + createComponentsHelper(flags, replyTo, viaBotId, markup); + initMediaFromDocument(doc, caption); setText(QString(), EntitiesInText()); } -HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption) : -HistoryItem(history, block, msgId, flags, date, from) -, _text(st::msgMinWidth) -, _textWidth(0) -, _textHeight(0) -, _via(viaBotId ? new HistoryMessageVia(viaBotId) : 0) -, _media(0) -, _views(fromChannel() ? 1 : -1) { - initTime(); - _media = new HistoryPhoto(photo, caption, this); - _media->regItem(this); +HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) +: HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { + createComponentsHelper(flags, replyTo, viaBotId, markup); + + _media.reset(this, new HistoryPhoto(photo, caption, this)); setText(QString(), EntitiesInText()); } +void HistoryMessage::createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, const MTPReplyMarkup &markup) { + CreateConfig config; + + if (flags & MTPDmessage::Flag::f_via_bot_id) config.viaBotId = viaBotId; + if (flags & MTPDmessage::Flag::f_reply_to_msg_id) config.replyTo = replyTo; + if (flags & MTPDmessage::Flag::f_reply_markup) config.markup = &markup; + if (isPost()) config.viewsCount = 1; + + createComponents(config); +} + +void HistoryMessage::createComponents(const CreateConfig &config) { + uint64 mask = 0; + if (config.replyTo) { + mask |= HistoryMessageReply::Bit(); + } + if (config.viaBotId) { + mask |= HistoryMessageVia::Bit(); + } + if (config.viewsCount >= 0) { + mask |= HistoryMessageViews::Bit(); + } + if (isPost() && _from->isUser()) { + mask |= HistoryMessageSigned::Bit(); + } + if (config.authorIdOriginal && config.fromIdOriginal) { + mask |= HistoryMessageForwarded::Bit(); + } + if (config.markup) { + // optimization: don't create markup component for the case + // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag + if (config.markup->type() != mtpc_replyKeyboardHide || config.markup->c_replyKeyboardHide().vflags.v != 0) { + mask |= HistoryMessageReplyMarkup::Bit(); + } + } + UpdateComponents(mask); + if (auto *reply = Get()) { + reply->replyToMsgId = config.replyTo; + if (!reply->updateData(this) && App::api()) { + App::api()->requestMessageData(history()->peer->asChannel(), reply->replyToMsgId, new HistoryDependentItemCallback(fullId())); + } + } + if (auto *via = Get()) { + via->create(config.viaBotId); + } + if (auto *views = Get()) { + views->_views = config.viewsCount; + } + if (auto *msgsigned = Get()) { + msgsigned->create(_from->asUser(), date); + } + if (auto *fwd = Get()) { + fwd->_authorOriginal = App::peer(config.authorIdOriginal); + fwd->_fromOriginal = App::peer(config.fromIdOriginal); + fwd->_originalId = config.originalId; + } + if (auto *markup = Get()) { + markup->create(*config.markup); + } + initTime(); +} + QString formatViewsCount(int32 views) { if (views > 999999) { views /= 100000; @@ -6091,49 +6879,40 @@ QString formatViewsCount(int32 views) { } void HistoryMessage::initTime() { - _timeText = date.toString(cTimeFormat()); - _timeWidth = st::msgDateFont->width(_timeText); - - _viewsText = (_views >= 0) ? formatViewsCount(_views) : QString(); - _viewsWidth = _viewsText.isEmpty() ? 0 : st::msgDateFont->width(_viewsText); + if (auto *msgsigned = Get()) { + _timeWidth = msgsigned->maxWidth(); + } else { + _timeText = date.toString(cTimeFormat()); + _timeWidth = st::msgDateFont->width(_timeText); + } + if (auto *views = Get()) { + views->_viewsText = (views->_views >= 0) ? formatViewsCount(views->_views) : QString(); + views->_viewsWidth = views->_viewsText.isEmpty() ? 0 : st::msgDateFont->width(views->_viewsText); + } } void HistoryMessage::initMedia(const MTPMessageMedia *media, QString ¤tText) { switch (media ? media->type() : mtpc_messageMediaEmpty) { case mtpc_messageMediaContact: { const MTPDmessageMediaContact &d(media->c_messageMediaContact()); - _media = new HistoryContact(d.vuser_id.v, qs(d.vfirst_name), qs(d.vlast_name), qs(d.vphone_number)); + _media.reset(this, new HistoryContact(d.vuser_id.v, qs(d.vfirst_name), qs(d.vlast_name), qs(d.vphone_number))); } break; case mtpc_messageMediaGeo: { const MTPGeoPoint &point(media->c_messageMediaGeo().vgeo); if (point.type() == mtpc_geoPoint) { - const MTPDgeoPoint &d(point.c_geoPoint()); - _media = new HistoryImageLink(qsl("location:%1,%2").arg(d.vlat.v).arg(d.vlong.v)); + _media.reset(this, new HistoryLocation(LocationCoords(point.c_geoPoint()))); } } break; case mtpc_messageMediaVenue: { const MTPDmessageMediaVenue &d(media->c_messageMediaVenue()); if (d.vgeo.type() == mtpc_geoPoint) { - const MTPDgeoPoint &g(d.vgeo.c_geoPoint()); - _media = new HistoryImageLink(qsl("location:%1,%2").arg(g.vlat.v).arg(g.vlong.v), qs(d.vtitle), qs(d.vaddress)); + _media.reset(this, new HistoryLocation(LocationCoords(d.vgeo.c_geoPoint()), qs(d.vtitle), qs(d.vaddress))); } } break; case mtpc_messageMediaPhoto: { const MTPDmessageMediaPhoto &photo(media->c_messageMediaPhoto()); if (photo.vphoto.type() == mtpc_photo) { - _media = new HistoryPhoto(App::feedPhoto(photo.vphoto.c_photo()), qs(photo.vcaption), this); - } - } break; - case mtpc_messageMediaVideo: { - const MTPDmessageMediaVideo &video(media->c_messageMediaVideo()); - if (video.vvideo.type() == mtpc_video) { - _media = new HistoryVideo(video.vvideo.c_video(), qs(video.vcaption), this); - } - } break; - case mtpc_messageMediaAudio: { - const MTPAudio &audio(media->c_messageMediaAudio().vaudio); - if (audio.type() == mtpc_audio) { - _media = new HistoryAudio(audio.c_audio()); + _media.reset(this, new HistoryPhoto(App::feedPhoto(photo.vphoto.c_photo()), qs(photo.vcaption), this)); } } break; case mtpc_messageMediaDocument: { @@ -6147,26 +6926,26 @@ void HistoryMessage::initMedia(const MTPMessageMedia *media, QString ¤tTex switch (d.type()) { case mtpc_webPageEmpty: break; case mtpc_webPagePending: { - _media = new HistoryWebPage(App::feedWebPage(d.c_webPagePending())); + _media.reset(this, new HistoryWebPage(App::feedWebPage(d.c_webPagePending()))); } break; case mtpc_webPage: { - _media = new HistoryWebPage(App::feedWebPage(d.c_webPage())); + _media.reset(this, new HistoryWebPage(App::feedWebPage(d.c_webPage()))); } break; } } break; }; - if (_media) _media->regItem(this); } void HistoryMessage::initMediaFromDocument(DocumentData *doc, const QString &caption) { if (doc->sticker()) { - _media = new HistorySticker(doc); + _media.reset(this, new HistorySticker(doc)); } else if (doc->isAnimation()) { - _media = new HistoryGif(doc, caption, this); + _media.reset(this, new HistoryGif(doc, caption, this)); + } else if (doc->isVideo()) { + _media.reset(this, new HistoryVideo(doc, caption, this)); } else { - _media = new HistoryDocument(doc, caption, this); + _media.reset(this, new HistoryDocument(doc, caption, this)); } - _media->regItem(this); } int32 HistoryMessage::plainMaxWidth() const { @@ -6175,6 +6954,12 @@ int32 HistoryMessage::plainMaxWidth() const { void HistoryMessage::initDimensions() { if (drawBubble()) { + auto fwd = Get(); + auto via = Get(); + if (fwd) { + fwd->create(via); + } + if (_media) { _media->initDimensions(this); if (_media->isDisplayed()) { @@ -6201,9 +6986,24 @@ void HistoryMessage::initDimensions() { if (maxw > _maxw) _maxw = maxw; _minh += _media->minHeight(); } - if (!_media && !displayFromName() && via() && !toHistoryForwarded()) { - if (st::msgPadding.left() + via()->maxWidth + st::msgPadding.right() > _maxw) { - _maxw = st::msgPadding.left() + via()->maxWidth + st::msgPadding.right(); + if (!_media) { + if (displayFromName()) { + int32 namew = st::msgPadding.left() + author()->nameText.maxWidth() + st::msgPadding.right(); + if (via && !fwd) { + namew += st::msgServiceFont->spacew + via->_maxWidth; + } + if (namew > _maxw) _maxw = namew; + } else if (via && !fwd) { + if (st::msgPadding.left() + via->_maxWidth + st::msgPadding.right() > _maxw) { + _maxw = st::msgPadding.left() + via->_maxWidth + st::msgPadding.right(); + } + } + if (fwd) { + int32 _namew = st::msgPadding.left() + fwd->_text.maxWidth() + st::msgPadding.right(); + if (via) { + _namew += st::msgServiceFont->spacew + via->_maxWidth; + } + if (_namew > _maxw) _maxw = _namew; } } } else { @@ -6211,44 +7011,99 @@ void HistoryMessage::initDimensions() { _maxw = _media->maxWidth(); _minh = _media->minHeight(); } - fromNameUpdated(); + if (auto *reply = Get()) { + reply->updateName(); + if (!_text.isEmpty()) { + int replyw = st::msgPadding.left() + reply->_maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right(); + if (reply->_replyToVia) { + replyw += st::msgServiceFont->spacew + reply->_replyToVia->_maxWidth; + } + if (replyw > _maxw) _maxw = replyw; + } + } + if (HistoryMessageReplyMarkup *markup = inlineReplyMarkup()) { + if (!markup->inlineKeyboard) { + markup->inlineKeyboard.reset(new ReplyKeyboard(this, MakeUnique(st::msgBotKbButton))); + } + + // if we have a text bubble we can resize it to fit the keyboard + // but if we have only media we don't do that + if (!_text.isEmpty()) { + _maxw = qMax(_maxw, markup->inlineKeyboard->naturalWidth()); + } + } } void HistoryMessage::countPositionAndSize(int32 &left, int32 &width) const { - int32 mwidth = qMin(int(st::msgMaxWidth), _maxw); - if (_media && _media->currentWidth() < mwidth) { - mwidth = qMax(_media->currentWidth(), qMin(mwidth, plainMaxWidth())); + int32 maxwidth = qMin(int(st::msgMaxWidth), _maxw), hwidth = _history->width; + if (_media && _media->currentWidth() < maxwidth) { + maxwidth = qMax(_media->currentWidth(), qMin(maxwidth, plainMaxWidth())); } - left = (!fromChannel() && out()) ? st::msgMargin.right() : st::msgMargin.left(); - if (displayFromPhoto()) { + left = (!isPost() && out() && !Adaptive::Wide()) ? st::msgMargin.right() : st::msgMargin.left(); + if (hasFromPhoto()) { left += st::msgPhotoSkip; +// } else if (!Adaptive::Wide() && !out() && !fromChannel() && st::msgPhotoSkip - (hmaxwidth - hwidth) > 0) { +// left += st::msgPhotoSkip - (hmaxwidth - hwidth); } - width = _history->width - st::msgMargin.left() - st::msgMargin.right(); - if (width > mwidth) { - if (!fromChannel() && out()) { - left += width - mwidth; + width = hwidth - st::msgMargin.left() - st::msgMargin.right(); + if (width > maxwidth) { + if (!isPost() && out() && !Adaptive::Wide()) { + left += width - maxwidth; } - width = mwidth; + width = maxwidth; } } -void HistoryMessage::fromNameUpdated() const { - if (!_media && displayFromName()) { - int32 namew = st::msgPadding.left() + _from->nameText.maxWidth() + st::msgPadding.right(); - if (via() && !toHistoryForwarded()) { - namew += st::msgServiceFont->spacew + via()->maxWidth; +void HistoryMessage::fromNameUpdated(int32 width) const { + _authorNameVersion = author()->nameVersion; + if (!Has()) { + if (auto *via = Get()) { + via->resize(width - st::msgPadding.left() - st::msgPadding.right() - author()->nameText.maxWidth() - st::msgServiceFont->spacew); } - if (namew > _maxw) _maxw = namew; } } +void HistoryMessage::applyEdition(const MTPDmessage &message) { + EntitiesInText entities; + if (message.has_entities()) { + entities = entitiesFromMTP(message.ventities.c_vector().v); + } + setText(qs(message.vmessage), entities); + setMedia(message.has_media() ? (&message.vmedia) : nullptr); + setReplyMarkup(message.has_reply_markup() ? (&message.vreply_markup) : nullptr); + setViewsCount(message.has_views() ? message.vviews.v : -1); + + setPendingInitDimensions(); + if (App::main()) { + App::main()->dlgUpdated(history(), id); + } + + // invalidate cache for drawInDialog + if (history()->textCachedFor == this) { + history()->textCachedFor = nullptr; + } +} + +void HistoryMessage::updateMedia(const MTPMessageMedia *media) { + bool fromInlineBot = (_flags & MTPDmessage_ClientFlag::f_from_inline_bot); + if (media && _media && _media->type() != MediaTypeWebPage && !fromInlineBot) { + _media->updateFrom(*media, this); + } else { + setMedia(media); + } + if (fromInlineBot) { + _flags &= ~MTPDmessage_ClientFlag::f_from_inline_bot; + } + setPendingInitDimensions(); +} + int32 HistoryMessage::addToOverview(AddToOverviewMethod method) { if (!indexInOverview()) return 0; int32 result = 0; - if (HistoryMedia *media = getMedia(true)) { + if (HistoryMedia *media = getMedia()) { MediaOverviewType type = mediaToOverviewType(media); if (type != OverviewCount) { if (history()->addToOverview(type, id, method)) { @@ -6265,7 +7120,7 @@ int32 HistoryMessage::addToOverview(AddToOverviewMethod method) { } void HistoryMessage::eraseFromOverview() { - if (HistoryMedia *media = getMedia(true)) { + if (HistoryMedia *media = getMedia()) { MediaOverviewType type = mediaToOverviewType(media); if (type != OverviewCount) { history()->eraseFromOverview(type, id); @@ -6277,31 +7132,49 @@ void HistoryMessage::eraseFromOverview() { } QString HistoryMessage::selectedText(uint32 selection) const { + QString result; if (_media && selection == FullSelection) { QString text = _text.original(0, 0xFFFF, Text::ExpandLinksAll), mediaText = _media->inHistoryText(); - return text.isEmpty() ? mediaText : (mediaText.isEmpty() ? text : (text + ' ' + mediaText)); + result = text.isEmpty() ? mediaText : (mediaText.isEmpty() ? text : (text + ' ' + mediaText)); + } else { + uint16 selectedFrom = (selection == FullSelection) ? 0 : ((selection >> 16) & 0xFFFF); + uint16 selectedTo = (selection == FullSelection) ? 0xFFFF : (selection & 0xFFFF); + result = _text.original(selectedFrom, selectedTo, Text::ExpandLinksAll); } - uint16 selectedFrom = (selection == FullSelection) ? 0 : ((selection >> 16) & 0xFFFF); - uint16 selectedTo = (selection == FullSelection) ? 0xFFFF : (selection & 0xFFFF); - return _text.original(selectedFrom, selectedTo, Text::ExpandLinksAll); + if (auto *fwd = Get()) { + if (selection == FullSelection) { + QString fwdinfo = fwd->_text.original(0, 0xFFFF, Text::ExpandLinksAll), wrapped; + wrapped.reserve(fwdinfo.size() + 4 + result.size()); + wrapped.append('[').append(fwdinfo).append(qsl("]\n")).append(result); + result = wrapped; + } + } + if (auto *reply = Get()) { + if (selection == FullSelection && reply->replyToMsg) { + QString wrapped; + wrapped.reserve(lang(lng_in_reply_to).size() + reply->replyToMsg->author()->name.size() + 4 + result.size()); + wrapped.append('[').append(lang(lng_in_reply_to)).append(' ').append(reply->replyToMsg->author()->name).append(qsl("]\n")).append(result); + result = wrapped; + } + } + return result; } QString HistoryMessage::inDialogsText() const { return emptyText() ? (_media ? _media->inDialogsText() : QString()) : _text.original(0, 0xFFFF, Text::ExpandLinksNone); } -HistoryMedia *HistoryMessage::getMedia(bool inOverview) const { - return _media; +HistoryMedia *HistoryMessage::getMedia() const { + return _media.data(); } -void HistoryMessage::setMedia(const MTPMessageMedia *media, bool allowEmitResize) { - if ((!_media || _media->isImageLink()) && (!media || media->type() == mtpc_messageMediaEmpty)) return; +void HistoryMessage::setMedia(const MTPMessageMedia *media) { + if (!_media && (!media || media->type() == mtpc_messageMediaEmpty)) return; bool mediaWasDisplayed = false; if (_media) { mediaWasDisplayed = _media->isDisplayed(); - delete _media; - _media = 0; + _media.clear(this); } QString t; initMedia(media, t); @@ -6314,12 +7187,10 @@ void HistoryMessage::setMedia(const MTPMessageMedia *media, bool allowEmitResize _textWidth = 0; _textHeight = 0; } - initDimensions(); - if (allowEmitResize) Notify::historyItemResized(this); } void HistoryMessage::setText(const QString &text, const EntitiesInText &entities) { - textstyleSet(&((out() && !fromChannel()) ? st::outTextStyle : st::inTextStyle)); + textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); if (_media && _media->isDisplayed()) { _text.setMarkedText(st::msgFont, text, entities, itemTextOptions(this)); } else { @@ -6329,7 +7200,7 @@ void HistoryMessage::setText(const QString &text, const EntitiesInText &entities for (int32 i = 0, l = entities.size(); i != l; ++i) { if (entities.at(i).type == EntityInTextUrl || entities.at(i).type == EntityInTextCustomUrl || entities.at(i).type == EntityInTextEmail) { - _flags |= MTPDmessage_flag_HAS_TEXT_LINKS; + _flags |= MTPDmessage_ClientFlag::f_has_text_links; break; } } @@ -6337,6 +7208,29 @@ void HistoryMessage::setText(const QString &text, const EntitiesInText &entities _textHeight = 0; } +void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) { + if (!markup && !(_flags & MTPDmessage::Flag::f_reply_markup)) return; + + // optimization: don't create markup component for the case + // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag + if (markup->type() == mtpc_replyKeyboardHide && markup->c_replyKeyboardHide().vflags.v == 0) { + if (Has()) { + RemoveComponents(HistoryMessageReplyMarkup::Bit()); + setPendingInitDimensions(); + + Notify::replyMarkupUpdated(this); + } + } else { + if (!Has()) { + AddComponents(HistoryMessageReplyMarkup::Bit()); + } + Get()->create(*markup); + setPendingInitDimensions(); + + Notify::replyMarkupUpdated(this); + } +} + QString HistoryMessage::originalText() const { return emptyText() ? QString() : _text.original(); } @@ -6352,18 +7246,24 @@ bool HistoryMessage::textHasLinks() { void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const { p.setFont(st::msgDateFont); - bool outbg = out() && !fromChannel(), overimg = (type == InfoDisplayOverImage); + bool outbg = out() && !isPost(); + bool invertedsprites = (type == InfoDisplayOverImage || type == InfoDisplayOverBackground); int32 infoRight = right, infoBottom = bottom; switch (type) { case InfoDisplayDefault: infoRight -= st::msgPadding.right() - st::msgDateDelta.x(); infoBottom -= st::msgPadding.bottom() - st::msgDateDelta.y(); - p.setPen((selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg))->p); + p.setPen(selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg)); break; case InfoDisplayOverImage: infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x(); infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y(); - p.setPen(st::msgDateImgColor->p); + p.setPen(st::msgDateImgColor); + break; + case InfoDisplayOverBackground: + infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x(); + infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y(); + p.setPen(st::msgServiceColor); break; } @@ -6375,59 +7275,67 @@ void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, int32 width if (type == InfoDisplayOverImage) { int32 dateW = infoW + 2 * st::msgDateImgPadding.x(), dateH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); App::roundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + } else if (type == InfoDisplayOverBackground) { + int32 dateW = infoW + 2 * st::msgDateImgPadding.x(), dateH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); + App::roundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, selected ? App::msgServiceSelectBg() : App::msgServiceBg(), selected ? ServiceSelectedCorners : ServiceCorners); } dateX += HistoryMessage::timeLeft(); - p.drawText(dateX, dateY + st::msgDateFont->ascent, _timeText); + if (auto *msgsigned = Get()) { + msgsigned->_signature.drawElided(p, dateX, dateY, _timeWidth); + } else { + p.drawText(dateX, dateY + st::msgDateFont->ascent, _timeText); + } QPoint iconPos; const QRect *iconRect = 0; - if (!_viewsText.isEmpty()) { + if (auto *views = Get()) { iconPos = QPoint(infoRight - infoW + st::msgViewsPos.x(), infoBottom - st::msgViewsImg.pxHeight() + st::msgViewsPos.y()); if (id > 0) { - if (out() && !fromChannel()) { - iconRect = &(overimg ? st::msgInvViewsImg : (selected ? st::msgSelectOutViewsImg : st::msgOutViewsImg)); + if (outbg) { + iconRect = &(invertedsprites ? st::msgInvViewsImg : (selected ? st::msgSelectOutViewsImg : st::msgOutViewsImg)); } else { - iconRect = &(overimg ? st::msgInvViewsImg : (selected ? st::msgSelectViewsImg : st::msgViewsImg)); + iconRect = &(invertedsprites ? st::msgInvViewsImg : (selected ? st::msgSelectViewsImg : st::msgViewsImg)); } - p.drawText(iconPos.x() + st::msgViewsImg.pxWidth() + st::msgDateCheckSpace, infoBottom - st::msgDateFont->descent, _viewsText); + p.drawText(iconPos.x() + st::msgViewsImg.pxWidth() + st::msgDateCheckSpace, infoBottom - st::msgDateFont->descent, views->_viewsText); } else { - iconPos.setX(iconPos.x() + st::msgDateViewsSpace + _viewsWidth); - if (out() && !fromChannel()) { - iconRect = &(overimg ? st::msgInvSendingViewsImg : st::msgSendingOutViewsImg); + iconPos.setX(iconPos.x() + st::msgDateViewsSpace + views->_viewsWidth); + if (outbg) { + iconRect = &(invertedsprites ? st::msgInvSendingViewsImg : st::msgSendingOutViewsImg); } else { - iconRect = &(overimg ? st::msgInvSendingViewsImg : st::msgSendingViewsImg); + iconRect = &(invertedsprites ? st::msgInvSendingViewsImg : st::msgSendingViewsImg); } } p.drawPixmap(iconPos, App::sprite(), *iconRect); } else if (id < 0 && history()->peer->isSelf()) { iconPos = QPoint(infoRight - infoW, infoBottom - st::msgViewsImg.pxHeight() + st::msgViewsPos.y()); - iconRect = &(overimg ? st::msgInvSendingViewsImg : st::msgSendingViewsImg); + iconRect = &(invertedsprites ? st::msgInvSendingViewsImg : st::msgSendingViewsImg); p.drawPixmap(iconPos, App::sprite(), *iconRect); } - if (out() && !fromChannel()) { + if (outbg) { iconPos = QPoint(infoRight - st::msgCheckImg.pxWidth() + st::msgCheckPos.x(), infoBottom - st::msgCheckImg.pxHeight() + st::msgCheckPos.y()); if (id > 0) { if (unread()) { - iconRect = &(overimg ? st::msgInvCheckImg : (selected ? st::msgSelectCheckImg : st::msgCheckImg)); + iconRect = &(invertedsprites ? st::msgInvCheckImg : (selected ? st::msgSelectCheckImg : st::msgCheckImg)); } else { - iconRect = &(overimg ? st::msgInvDblCheckImg : (selected ? st::msgSelectDblCheckImg : st::msgDblCheckImg)); + iconRect = &(invertedsprites ? st::msgInvDblCheckImg : (selected ? st::msgSelectDblCheckImg : st::msgDblCheckImg)); } } else { - iconRect = &(overimg ? st::msgInvSendingImg : st::msgSendingImg); + iconRect = &(invertedsprites ? st::msgInvSendingImg : st::msgSendingImg); } p.drawPixmap(iconPos, App::sprite(), *iconRect); } } void HistoryMessage::setViewsCount(int32 count) { - if (_views == count || (count >= 0 && _views > count)) return; + auto *views = Get(); + if (!views || views->_views == count || (count >= 0 && views->_views > count)) return; - int32 was = _viewsWidth; - _views = count; - _viewsText = (_views >= 0) ? formatViewsCount(_views) : QString(); - _viewsWidth = _viewsText.isEmpty() ? 0 : st::msgDateFont->width(_viewsText); - if (was == _viewsWidth) { + int32 was = views->_viewsWidth; + views->_views = count; + views->_viewsText = (views->_views >= 0) ? formatViewsCount(views->_views) : QString(); + views->_viewsWidth = views->_viewsText.isEmpty() ? 0 : st::msgDateFont->width(views->_viewsText); + if (was == views->_viewsWidth) { Ui::repaintHistoryItem(this); } else { if (_text.hasSkipBlock()) { @@ -6435,8 +7343,7 @@ void HistoryMessage::setViewsCount(int32 count) { _textWidth = 0; _textHeight = 0; } - initDimensions(); - Notify::historyItemResized(this); + setPendingInitDimensions(); } } @@ -6451,15 +7358,28 @@ void HistoryMessage::setId(MsgId newId) { _textWidth = 0; _textHeight = 0; } - initDimensions(); - Notify::historyItemResized(this); + setPendingInitDimensions(); } } void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { - bool outbg = out() && !fromChannel(), bubble = drawBubble(), selected = (selection == FullSelection); + bool outbg = out() && !isPost(), bubble = drawBubble(), selected = (selection == FullSelection); - textstyleSet(&(outbg ? st::outTextStyle : st::inTextStyle)); + int left = 0, width = 0, height = _height; + countPositionAndSize(left, width); + if (width < 1) return; + + int dateh = 0, unreadbarh = 0; + if (auto *date = Get()) { + dateh = date->height(); + date->paint(p, 0, _history->width); + } + if (auto *unreadbar = Get()) { + unreadbarh = unreadbar->height(); + p.translate(0, dateh); + unreadbar->paint(p, 0, _history->width); + p.translate(0, -dateh); + } uint64 animms = App::main() ? App::main()->animActiveTimeStart(this) : 0; if (animms > 0 && animms <= ms) { @@ -6467,28 +7387,41 @@ void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 m if (animms > st::activeFadeInDuration + st::activeFadeOutDuration) { App::main()->stopAnimActive(); } else { + int skiph = marginTop() - marginBottom(); + float64 dt = (animms > st::activeFadeInDuration) ? (1 - (animms - st::activeFadeInDuration) / float64(st::activeFadeOutDuration)) : (animms / float64(st::activeFadeInDuration)); float64 o = p.opacity(); p.setOpacity(o * dt); - p.fillRect(0, 0, _history->width, _height, textstyleCurrent()->selectOverlay->b); + p.fillRect(0, skiph, _history->width, height - skiph, textstyleCurrent()->selectOverlay->b); p.setOpacity(o); } } - if (_from->nameVersion > _fromVersion) { -// fromNameUpdated(); - _fromVersion = _from->nameVersion; + textstyleSet(&(outbg ? st::outTextStyle : st::inTextStyle)); + + if (const ReplyKeyboard *keyboard = inlineReplyKeyboard()) { + int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); + height -= h; + int top = height + st::msgBotKbButton.margin - marginBottom(); + p.translate(left, top); + keyboard->paint(p, r.translated(-left, -top)); + p.translate(-left, -top); } - int32 left = 0, width = 0; - countPositionAndSize(left, width); - if (displayFromPhoto()) { - p.drawPixmap(left - st::msgPhotoSkip, _height - st::msgMargin.bottom() - st::msgPhotoSize, _from->photo->pixRounded(st::msgPhotoSize)); + auto *reply = Get(); + if (reply) { + reply->checkNameUpdate(); } - if (width < 1) return; if (bubble) { - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); + auto *fwd = Get(); + auto *via = Get(); + if (displayFromName() && author()->nameVersion > _authorNameVersion) { + fromNameUpdated(width); + } + + int32 top = marginTop(); + QRect r(left, top, width, height - top - marginBottom()); style::color bg(selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg)); style::color sh(selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow)); @@ -6497,28 +7430,36 @@ void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 m if (displayFromName()) { p.setFont(st::msgNameFont); - if (fromChannel()) { + if (isPost()) { p.setPen(selected ? st::msgInServiceFgSelected : st::msgInServiceFg); } else { - p.setPen(_from->color); + p.setPen(author()->color); } - _from->nameText.drawElided(p, r.left() + st::msgPadding.left(), r.top() + st::msgPadding.top(), width - st::msgPadding.left() - st::msgPadding.right()); - if (via() && !toHistoryForwarded() && width > st::msgPadding.left() + st::msgPadding.right() + _from->nameText.maxWidth() + st::msgServiceFont->spacew) { + author()->nameText.drawElided(p, r.left() + st::msgPadding.left(), r.top() + st::msgPadding.top(), width - st::msgPadding.left() - st::msgPadding.right()); + if (via && !fwd && width > st::msgPadding.left() + st::msgPadding.right() + author()->nameText.maxWidth() + st::msgServiceFont->spacew) { p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); - p.drawText(r.left() + st::msgPadding.left() + _from->nameText.maxWidth() + st::msgServiceFont->spacew, r.top() + st::msgPadding.top() + st::msgServiceFont->ascent, via()->text); + p.drawText(r.left() + st::msgPadding.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew, r.top() + st::msgPadding.top() + st::msgServiceFont->ascent, via->_text); } r.setTop(r.top() + st::msgNameFont->height); } QRect trect(r.marginsAdded(-st::msgPadding)); - drawMessageText(p, trect, selection); + + paintForwardedInfo(p, trect, selected); + paintReplyInfo(p, trect, selected); + paintViaBotIdInfo(p, trect, selected); + + p.setPen(st::msgColor); + p.setFont(st::msgFont); + uint16 selectedFrom = selected ? 0 : ((selection >> 16) & 0xFFFF); + uint16 selectedTo = selected ? 0 : (selection & 0xFFFF); + _text.draw(p, trect.x(), trect.y(), trect.width(), style::al_left, 0, -1, selectedFrom, selectedTo); if (_media && _media->isDisplayed()) { - p.save(); - int32 top = _height - st::msgMargin.bottom() - _media->height(); + int32 top = height - marginBottom() - _media->height(); p.translate(left, top); _media->draw(p, this, r.translated(-left, -top), selected, ms); - p.restore(); + p.translate(-left, -top); if (!_media->customInfoLayout()) { HistoryMessage::drawInfo(p, r.x() + r.width(), r.y() + r.height(), 2 * r.x() + r.width(), selected, InfoDisplayDefault); } @@ -6526,30 +7467,61 @@ void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 m HistoryMessage::drawInfo(p, r.x() + r.width(), r.y() + r.height(), 2 * r.x() + r.width(), selected, InfoDisplayDefault); } } else { - p.save(); - int32 top = st::msgMargin.top(); + int32 top = marginTop(); p.translate(left, top); _media->draw(p, this, r.translated(-left, -top), selected, ms); - p.restore(); + p.translate(-left, -top); } textstyleRestore(); } -void HistoryMessage::drawMessageText(Painter &p, QRect trect, uint32 selection) const { - bool outbg = out() && !fromChannel(), selected = (selection == FullSelection); - if (!displayFromName() && via() && !toHistoryForwarded()) { - p.setFont(st::msgServiceNameFont); - p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); - p.drawTextLeft(trect.left(), trect.top(), _history->width, via()->text); - trect.setY(trect.y() + st::msgServiceNameFont->height); - } +void HistoryMessage::paintForwardedInfo(Painter &p, QRect &trect, bool selected) const { + if (displayForwardedFrom()) { + style::font serviceFont(st::msgServiceFont), serviceName(st::msgServiceNameFont); - p.setPen(st::msgColor); - p.setFont(st::msgFont); - uint16 selectedFrom = (selection == FullSelection) ? 0 : (selection >> 16) & 0xFFFF; - uint16 selectedTo = (selection == FullSelection) ? 0 : selection & 0xFFFF; - _text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignLeft, 0, -1, selectedFrom, selectedTo); + p.setPen(selected ? (hasOutLayout() ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (hasOutLayout() ? st::msgOutServiceFg : st::msgInServiceFg)); + p.setFont(serviceFont); + + auto *fwd = Get(); + bool breakEverywhere = (fwd->_text.countHeight(trect.width()) > 2 * serviceFont->height); + textstyleSet(&(selected ? (hasOutLayout() ? st::outFwdTextStyleSelected : st::inFwdTextStyleSelected) : (hasOutLayout() ? st::outFwdTextStyle : st::inFwdTextStyle))); + fwd->_text.drawElided(p, trect.x(), trect.y(), trect.width(), 2, style::al_left, 0, -1, 0, breakEverywhere); + textstyleSet(&(hasOutLayout() ? st::outTextStyle : st::inTextStyle)); + + trect.setY(trect.y() + (((fwd->_text.maxWidth() > trect.width()) ? 2 : 1) * serviceFont->height)); + } +} + +void HistoryMessage::paintReplyInfo(Painter &p, QRect &trect, bool selected) const { + if (auto *reply = Get()) { + int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); + + HistoryMessageReply::PaintFlags flags = HistoryMessageReply::PaintInBubble; + if (selected) { + flags |= HistoryMessageReply::PaintSelected; + } + reply->paint(p, this, trect.x(), trect.y(), trect.width(), flags); + + trect.setY(trect.y() + h); + } +} + +void HistoryMessage::paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const { + if (!displayFromName() && !Has()) { + if (auto *via = Get()) { + p.setFont(st::msgServiceNameFont); + p.setPen(selected ? (hasOutLayout() ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (hasOutLayout() ? st::msgOutServiceFg : st::msgInServiceFg)); + p.drawTextLeft(trect.left(), trect.top(), _history->width, via->_text); + trect.setY(trect.y() + st::msgServiceNameFont->height); + } + } +} + +void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) { + if (auto *reply = Get()) { + reply->itemRemoved(this, dependency); + } } void HistoryMessage::destroy() { @@ -6557,7 +7529,7 @@ void HistoryMessage::destroy() { HistoryItem::destroy(); } -int32 HistoryMessage::resize(int32 width) { +int HistoryMessage::resizeGetHeight_(int width) { if (width < st::msgMinWidth) return _height; width -= st::msgMargin.left() + st::msgMargin.right(); @@ -6567,59 +7539,97 @@ int32 HistoryMessage::resize(int32 width) { width = st::msgMaxWidth; } if (drawBubble()) { + auto *fwd = Get(); + auto *reply = Get(); + auto *via = Get(); + bool media = (_media && _media->isDisplayed()); if (width >= _maxw) { _height = _minh; - if (media) _media->resize(_maxw, this); + if (media) _media->resizeGetHeight(_maxw, this); } else { if (_text.isEmpty()) { _height = 0; } else { int32 textWidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 1); if (textWidth != _textWidth) { - textstyleSet(&((out() && !fromChannel()) ? st::outTextStyle : st::inTextStyle)); + textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); _textWidth = textWidth; _textHeight = _text.countHeight(textWidth); textstyleRestore(); } _height = st::msgPadding.top() + _textHeight + st::msgPadding.bottom(); } - if (media) _height += _media->resize(width, this); + if (media) _height += _media->resizeGetHeight(width, this); } + if (displayFromName()) { if (emptyText()) { _height += st::msgPadding.top() + st::msgNameFont->height + st::mediaHeaderSkip; } else { _height += st::msgNameFont->height; } - if (via() && !toHistoryForwarded()) { - via()->resize(width - st::msgPadding.left() - st::msgPadding.right() - _from->nameText.maxWidth() - st::msgServiceFont->spacew); - } - } else if (via() && !toHistoryForwarded()) { - via()->resize(width - st::msgPadding.left() - st::msgPadding.right()); + int32 l = 0, w = 0; + countPositionAndSize(l, w); + fromNameUpdated(w); + } else if (via && !fwd) { + int32 l = 0, w = 0; + countPositionAndSize(l, w); + via->resize(w - st::msgPadding.left() - st::msgPadding.right()); if (emptyText() && !displayFromName()) { _height += st::msgPadding.top() + st::msgNameFont->height + st::mediaHeaderSkip; } else { _height += st::msgNameFont->height; } } + + if (displayForwardedFrom()) { + int32 l = 0, w = 0; + countPositionAndSize(l, w); + int32 fwdheight = ((fwd->_text.maxWidth() > (w - st::msgPadding.left() - st::msgPadding.right())) ? 2 : 1) * st::semiboldFont->height; + + if (emptyText() && !displayFromName()) { + _height += st::msgPadding.top() + fwdheight + st::mediaHeaderSkip; + } else { + _height += fwdheight; + } + } + + if (reply) { + if (emptyText() && !displayFromName() && !Has()) { + _height += st::msgPadding.top() + st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom() + st::mediaHeaderSkip; + } else { + _height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); + } + reply->resize(width - st::msgPadding.left() - st::msgPadding.right()); + } } else { - _height = _media->resize(width, this); + _height = _media->resizeGetHeight(width, this); } - _height += st::msgMargin.top() + st::msgMargin.bottom(); + if (ReplyKeyboard *keyboard = inlineReplyKeyboard()) { + int32 l = 0, w = 0; + countPositionAndSize(l, w); + + int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); + _height += h; + keyboard->resize(w, h - st::msgBotKbButton.margin); + } + + _height += marginTop() + marginBottom(); return _height; } bool HistoryMessage::hasPoint(int32 x, int32 y) const { - int32 left = 0, width = 0; + int left = 0, width = 0, height = _height; countPositionAndSize(left, width); if (width < 1) return false; if (drawBubble()) { - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); + int top = marginTop(); + QRect r(left, top, width, height - top - marginBottom()); return r.contains(x, y); } else { - return _media->hasPoint(x - left, y - st::msgMargin.top(), this); + return _media->hasPoint(x - left, y - marginTop(), this); } } @@ -6640,80 +7650,108 @@ bool HistoryMessage::pointInTime(int32 right, int32 bottom, int32 x, int32 y, In return QRect(dateX, dateY, HistoryMessage::timeWidth(), st::msgDateFont->height).contains(x, y); } -void HistoryMessage::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { +void HistoryMessage::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { + lnk.clear(); state = HistoryDefaultCursorState; - lnk = TextLinkPtr(); - int32 left = 0, width = 0; + int left = 0, width = 0, height = _height; countPositionAndSize(left, width); - if (displayFromPhoto()) { - if (x >= left - st::msgPhotoSkip && x < left - st::msgPhotoSkip + st::msgPhotoSize && y >= _height - st::msgMargin.bottom() - st::msgPhotoSize && y < _height - st::msgMargin.bottom()) { - lnk = _from->lnk; - return; - } - } + if (width < 1) return; + if (const ReplyKeyboard *keyboard = inlineReplyKeyboard()) { + int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); + height -= h; + int top = height + st::msgBotKbButton.margin - marginBottom(); + if (x >= left && x < left + width && y >= top && y < _height - marginBottom()) { + return keyboard->getState(lnk, x - left, y - top); + } + } + if (drawBubble()) { - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); - if (displayFromName()) { // from user left name - if (y >= r.top() + st::msgPadding.top() && y < r.top() + st::msgPadding.top() + st::msgNameFont->height) { - if (x >= r.left() + st::msgPadding.left() && x < r.left() + r.width() - st::msgPadding.right() && x < r.left() + st::msgPadding.left() + _from->nameText.maxWidth()) { - lnk = _from->lnk; + auto *fwd = Get(); + auto *via = Get(); + auto *reply = Get(); + + int top = marginTop(); + QRect r(left, top, width, height - top - marginBottom()); + QRect trect(r.marginsAdded(-st::msgPadding)); + if (displayFromName()) { + if (y >= trect.top() && y < trect.top() + st::msgNameFont->height) { + if (x >= trect.left() && x < trect.left() + trect.width() && x < trect.left() + author()->nameText.maxWidth()) { + lnk = author()->openLink(); return; } - if (via() && !toHistoryForwarded() && x >= r.left() + st::msgPadding.left() + _from->nameText.maxWidth() + st::msgServiceFont->spacew && x < r.left() + st::msgPadding.left() + _from->nameText.maxWidth() + st::msgServiceFont->spacew + via()->width) { - lnk = via()->lnk; + if (via && !fwd && x >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && x < trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew + via->_width) { + lnk = via->_lnk; return; } } - r.setTop(r.top() + st::msgNameFont->height); + trect.setTop(trect.top() + st::msgNameFont->height); } - getStateFromMessageText(lnk, state, x, y, r); - } else { - _media->getState(lnk, state, x - left, y - st::msgMargin.top(), this); - } -} - -void HistoryMessage::getStateFromMessageText(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const QRect &r) const { - bool inDate = false; - - QRect trect(r.marginsAdded(-st::msgPadding)); - - if (!displayFromName() && via() && !toHistoryForwarded()) { - if (x >= trect.left() && y >= trect.top() && y < trect.top() + st::msgNameFont->height && x < trect.left() + via()->width) { - lnk = via()->lnk; - return; + if (displayForwardedFrom()) { + int32 fwdheight = ((fwd->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; + if (y >= trect.top() && y < trect.top() + fwdheight) { + bool inText = false; + bool breakEverywhere = (fwd->_text.countHeight(trect.width()) > 2 * st::semiboldFont->height); + textstyleSet(&st::inFwdTextStyle); + fwd->_text.getState(lnk, inText, x - trect.left(), y - trect.top(), trect.width(), style::al_left, breakEverywhere); + textstyleRestore(); + if (breakEverywhere) { + state = HistoryInForwardedCursorState; + } + return; + } + trect.setTop(trect.top() + fwdheight); + } + if (via && !displayFromName() && !displayForwardedFrom()) { + if (x >= trect.left() && y >= trect.top() && y < trect.top() + st::msgNameFont->height && x < trect.left() + via->_width) { + lnk = via->_lnk; + return; + } + trect.setTop(trect.top() + st::msgNameFont->height); + } + if (reply) { + int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); + if (y >= trect.top() && y < trect.top() + h) { + if (reply->replyToMsg && y >= trect.top() + st::msgReplyPadding.top() && y < trect.top() + st::msgReplyPadding.top() + st::msgReplyBarSize.height() && x >= trect.left() && x < trect.left() + trect.width()) { + lnk = reply->replyToLink(); + } + return; + } + trect.setTop(trect.top() + h); } - trect.setTop(trect.top() + st::msgNameFont->height); - } - TextLinkPtr medialnk; - if (_media && _media->isDisplayed()) { - if (!_media->customInfoLayout()) { + bool inDate = false; + + if (_media && _media->isDisplayed()) { + if (!_media->customInfoLayout()) { + inDate = HistoryMessage::pointInTime(r.x() + r.width(), r.y() + r.height(), x, y, InfoDisplayDefault); + } + if (y >= r.bottom() - _media->height() && y < r.bottom()) { + _media->getState(lnk, state, x - r.left(), y - (r.bottom() - _media->height()), this); + if (inDate) state = HistoryInDateCursorState; + return; + } + trect.setBottom(trect.bottom() - _media->height()); + } else { inDate = HistoryMessage::pointInTime(r.x() + r.width(), r.y() + r.height(), x, y, InfoDisplayDefault); } - if (y >= r.bottom() - _media->height() && y < r.bottom()) { - _media->getState(lnk, state, x - r.left(), y - (r.bottom() - _media->height()), this); - if (inDate) state = HistoryInDateCursorState; - return; + + textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); + bool inText = false; + _text.getState(lnk, inText, x - trect.x(), y - trect.y(), trect.width()); + textstyleRestore(); + + if (inDate) { + state = HistoryInDateCursorState; + } else if (inText) { + state = HistoryInTextCursorState; + } else { + state = HistoryDefaultCursorState; } - trect.setBottom(trect.bottom() - _media->height()); } else { - inDate = HistoryMessage::pointInTime(r.x() + r.width(), r.y() + r.height(), x, y, InfoDisplayDefault); - } - - textstyleSet(&((out() && !fromChannel()) ? st::outTextStyle : st::inTextStyle)); - bool inText = false; - _text.getState(lnk, inText, x - trect.x(), y - trect.y(), trect.width()); - textstyleRestore(); - - if (inDate) { - state = HistoryInDateCursorState; - } else if (inText) { - state = HistoryInTextCursorState; - } else { - state = HistoryDefaultCursorState; + _media->getState(lnk, state, x - left, y - marginTop(), this); } } @@ -6721,23 +7759,37 @@ void HistoryMessage::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, symbol = 0; after = false; upon = false; + if (drawBubble()) { - int32 left = 0, width = 0; + int left = 0, width = 0, height = _height; countPositionAndSize(left, width); if (width < 1) return; - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); - if (displayFromName()) { // from user left name - r.setTop(r.top() + st::msgNameFont->height); - } else if (via() && !toHistoryForwarded()) { - r.setTop(r.top() + st::msgNameFont->height); - } + auto *fwd = Get(); + auto *via = Get(); + auto *reply = Get(); + + int top = marginTop(); + QRect r(left, top, width, height - top - marginBottom()); QRect trect(r.marginsAdded(-st::msgPadding)); + if (displayFromName()) { + trect.setTop(trect.top() + st::msgNameFont->height); + } else if (via && !fwd) { + trect.setTop(trect.top() + st::msgNameFont->height); + } + if (displayForwardedFrom()) { + int32 fwdheight = ((fwd->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; + trect.setTop(trect.top() + fwdheight); + } + if (reply) { + int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); + trect.setTop(trect.top() + h); + } if (_media && _media->isDisplayed()) { trect.setBottom(trect.bottom() - _media->height()); } - textstyleSet(&((out() && !fromChannel()) ? st::outTextStyle : st::inTextStyle)); + textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); _text.getSymbol(symbol, after, upon, x - trect.x(), y - trect.y(), trect.width()); textstyleRestore(); } @@ -6747,10 +7799,10 @@ void HistoryMessage::drawInDialog(Painter &p, const QRect &r, bool act, const Hi if (cacheFor != this) { cacheFor = this; QString msg(inDialogsText()); - if ((!_history->peer->isUser() || out()) && !fromChannel()) { + if ((!_history->peer->isUser() || out()) && !isPost()) { TextCustomTagsMap custom; custom.insert(QChar('c'), qMakePair(textcmdStartLink(1), textcmdStopLink())); - msg = lng_message_with_from(lt_from, textRichPrepare((_from == App::self()) ? lang(lng_from_you) : _from->shortName()), lt_message, textRichPrepare(msg)); + msg = lng_message_with_from(lt_from, textRichPrepare((author() == App::self()) ? lang(lng_from_you) : author()->shortName()), lt_message, textRichPrepare(msg)); cache.setRichText(st::dlgHistFont, msg, _textDlgOptions, custom); } else { cache.setText(st::dlgHistFont, msg, _textDlgOptions); @@ -6766,531 +7818,32 @@ void HistoryMessage::drawInDialog(Painter &p, const QRect &r, bool act, const Hi } QString HistoryMessage::notificationHeader() const { - return (!_history->peer->isUser() && !fromChannel()) ? from()->name : QString(); + return (!_history->peer->isUser() && !isPost()) ? from()->name : QString(); } QString HistoryMessage::notificationText() const { QString msg(inDialogsText()); - if (msg.size() > 0xFF) msg = msg.mid(0, 0xFF) + qsl(".."); + if (msg.size() > 0xFF) msg = msg.mid(0, 0xFF) + qsl("..."); return msg; } +bool HistoryMessage::displayFromPhoto() const { + return hasFromPhoto() && !isAttachedToPrevious(); +} + +bool HistoryMessage::hasFromPhoto() const { + return (Adaptive::Wide() || (!out() && !history()->peer->isUser())) && !isPost(); +} + HistoryMessage::~HistoryMessage() { - if (_media) { - _media->unregItem(this); - deleteAndMark(_media); - } - deleteAndMark(_via); - if (_flags & MTPDmessage::flag_reply_markup) { - App::clearReplyMarkup(channelId(), id); + _media.clear(this); + if (auto *reply = Get()) { + reply->clearData(this); } } -HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, const MTPDmessage &msg) -: HistoryMessage(history, block, msg) -, fwdDate(::date(msg.vfwd_date)) -, fwdFrom(App::peer(peerFromMTP(msg.vfwd_from_id))) -, fwdFromVersion(fwdFrom->nameVersion) -, fromWidth(st::msgServiceFont->width(lang(lng_forwarded_from)) + st::msgServiceFont->spacew) { -} - -HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, MsgId id, QDateTime date, int32 from, HistoryMessage *msg) -: HistoryMessage(history, block, id, newMessageFlags(history->peer) | (!history->peer->isChannel() && msg->getMedia() && (msg->getMedia()->type() == MediaTypeAudio/* || msg->getMedia()->type() == MediaTypeVideo*/) ? MTPDmessage::flag_media_unread : 0), msg->via() ? peerToUser(msg->viaBot()->id) : 0, date, from, msg->HistoryMessage::originalText(), msg->HistoryMessage::originalEntities(), msg->getMedia()) -, fwdDate(msg->dateForwarded()) -, fwdFrom(msg->fromForwarded()) -, fwdFromVersion(fwdFrom->nameVersion) -, fromWidth(st::msgServiceFont->width(lang(lng_forwarded_from)) + st::msgServiceFont->spacew) { -} - -QString HistoryForwarded::selectedText(uint32 selection) const { - if (selection != FullSelection) return HistoryMessage::selectedText(selection); - QString result, original = HistoryMessage::selectedText(selection); - result.reserve(lang(lng_forwarded_from).size() + fwdFrom->name.size() + 4 + original.size()); - result.append('[').append(lang(lng_forwarded_from)).append(' ').append(fwdFrom->name).append(qsl("]\n")).append(original); - return result; -} - -void HistoryForwarded::initDimensions() { - fwdNameUpdated(); - HistoryMessage::initDimensions(); - if (!_media) { - int32 _namew = st::msgPadding.left() + fromWidth + fwdFromName.maxWidth() + st::msgPadding.right(); - if (via()) { - _namew += st::msgServiceFont->spacew + via()->maxWidth; - } - if (_namew > _maxw) _maxw = _namew; - } -} - -void HistoryForwarded::fwdNameUpdated() const { - QString fwdName((via() && fwdFrom->isUser()) ? fwdFrom->asUser()->firstName : App::peerName(fwdFrom)); - fwdFromName.setText(st::msgServiceNameFont, fwdName, _textNameOptions); -} - -void HistoryForwarded::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { - if (drawBubble() && fwdFrom->nameVersion > fwdFromVersion) { - fwdNameUpdated(); - fwdFromVersion = fwdFrom->nameVersion; - } - HistoryMessage::draw(p, r, selection, ms); -} - -void HistoryForwarded::drawForwardedFrom(Painter &p, int32 x, int32 y, int32 w, bool selected) const { - style::font serviceFont(st::msgServiceFont), serviceName(st::msgServiceNameFont); - - bool outbg = out() && !fromChannel(); - p.setPen((selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg))->p); - p.setFont(serviceFont); - - if (via() && w > fromWidth + fwdFromName.maxWidth() + serviceFont->spacew) { - p.drawText(x, y + serviceFont->ascent, lang(lng_forwarded_from)); - - p.setFont(serviceName); - fwdFromName.draw(p, x + fromWidth, y, w - fromWidth); - - p.drawText(x + fromWidth + fwdFromName.maxWidth() + serviceFont->spacew, y + serviceFont->ascent, via()->text); - } else if (w > fromWidth) { - p.drawText(x, y + serviceFont->ascent, lang(lng_forwarded_from)); - - p.setFont(serviceName); - fwdFromName.drawElided(p, x + fromWidth, y, w - fromWidth); - } else { - p.drawText(x, y + serviceFont->ascent, serviceFont->elided(lang(lng_forwarded_from), w)); - } -} - -void HistoryForwarded::drawMessageText(Painter &p, QRect trect, uint32 selection) const { - if (displayForwardedFrom()) { - drawForwardedFrom(p, trect.x(), trect.y(), trect.width(), (selection == FullSelection)); - trect.setY(trect.y() + st::msgServiceNameFont->height); - } - HistoryMessage::drawMessageText(p, trect, selection); -} - -int32 HistoryForwarded::resize(int32 width) { - HistoryMessage::resize(width); - if (drawBubble()) { - if (displayForwardedFrom()) { - if (emptyText() && !displayFromName()) { - _height += st::msgPadding.top() + st::msgServiceNameFont->height + st::mediaHeaderSkip; - } else { - _height += st::msgServiceNameFont->height; - } - if (via()) { - via()->resize(width - st::msgPadding.left() - st::msgPadding.right() - fromWidth - fwdFromName.maxWidth() - st::msgServiceFont->spacew); - } - } - } - return _height; -} - -bool HistoryForwarded::hasPoint(int32 x, int32 y) const { - if (drawBubble() && displayForwardedFrom()) { - int32 left = 0, width = 0; - countPositionAndSize(left, width); - if (width < 1) return false; - - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); - return r.contains(x, y); - } - return HistoryMessage::hasPoint(x, y); -} - -void HistoryForwarded::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { - lnk = TextLinkPtr(); - state = HistoryDefaultCursorState; - - if (drawBubble() && displayForwardedFrom()) { - int32 left = 0, width = 0; - countPositionAndSize(left, width); - if (displayFromPhoto()) { - if (x >= left - st::msgPhotoSkip && x < left - st::msgPhotoSkip + st::msgPhotoSize) { - return HistoryMessage::getState(lnk, state, x, y); - } - } - if (width < 1) return; - - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); - if (displayFromName()) { - style::font nameFont(st::msgNameFont); - if (y >= r.top() + st::msgPadding.top() && y < r.top() + st::msgPadding.top() + nameFont->height) { - return HistoryMessage::getState(lnk, state, x, y); - } - r.setTop(r.top() + nameFont->height); - } - QRect trect(r.marginsAdded(-st::msgPadding)); - - if (y >= trect.top() && y < trect.top() + st::msgServiceNameFont->height) { - return getForwardedState(lnk, state, x - trect.left(), trect.right() - trect.left()); - } - y -= st::msgServiceNameFont->height; - } - return HistoryMessage::getState(lnk, state, x, y); -} - -void HistoryForwarded::getStateFromMessageText(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const QRect &r) const { - QRect realr(r); - if (drawBubble() && displayForwardedFrom()) { - realr.setHeight(r.height() - st::msgServiceNameFont->height); - } - HistoryMessage::getStateFromMessageText(lnk, state, x, y, realr); -} - -void HistoryForwarded::getForwardedState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 w) const { - state = HistoryDefaultCursorState; - if (x >= fromWidth && x < w && x < fromWidth + fwdFromName.maxWidth()) { - lnk = fwdFrom->lnk; - } else if (via() && x >= fromWidth + fwdFromName.maxWidth() + st::msgServiceFont->spacew && x < w && x < fromWidth + fwdFromName.maxWidth() + st::msgServiceFont->spacew + via()->maxWidth) { - lnk = via()->lnk; - } else { - lnk = TextLinkPtr(); - } -} - -void HistoryForwarded::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const { - symbol = 0; - after = false; - upon = false; - - if (drawBubble() && displayForwardedFrom()) { - int32 left = 0, width = 0; - countPositionAndSize(left, width); - if (width < 1) return; - - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); - if (displayFromName()) { - style::font nameFont(st::msgNameFont); - if (y >= r.top() + st::msgPadding.top() && y < r.top() + st::msgPadding.top() + nameFont->height) { - return HistoryMessage::getSymbol(symbol, after, upon, x, y); - } - r.setTop(r.top() + nameFont->height); - } - QRect trect(r.marginsAdded(-st::msgPadding)); - - y -= st::msgServiceNameFont->height; - } - return HistoryMessage::getSymbol(symbol, after, upon, x, y); -} - -HistoryReply::HistoryReply(History *history, HistoryBlock *block, const MTPDmessage &msg) : HistoryMessage(history, block, msg) -, replyToMsgId(msg.vreply_to_msg_id.v) -, replyToMsg(0) -, replyToVersion(0) -, _maxReplyWidth(0) -, _replyToVia(0) { - if (!updateReplyTo() && App::api()) { - App::api()->requestReplyTo(this, history->peer->asChannel(), replyToMsgId); - } -} - -HistoryReply::HistoryReply(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption) -: HistoryMessage(history, block, msgId, flags, viaBotId, date, from, doc, caption) -, replyToMsgId(replyTo) -, replyToMsg(0) -, replyToVersion(0) -, _maxReplyWidth(0) -, _replyToVia(0) { - if (!updateReplyTo() && App::api()) { - App::api()->requestReplyTo(this, history->peer->asChannel(), replyToMsgId); - } -} - -HistoryReply::HistoryReply(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption) -: HistoryMessage(history, block, msgId, flags, viaBotId, date, from, photo, caption) -, replyToMsgId(replyTo) -, replyToMsg(0) -, replyToVersion(0) -, _maxReplyWidth(0) -, _replyToVia(0) { - if (!updateReplyTo() && App::api()) { - App::api()->requestReplyTo(this, history->peer->asChannel(), replyToMsgId); - } - replyToNameUpdated(); -} - -QString HistoryReply::selectedText(uint32 selection) const { - if (selection != FullSelection || !replyToMsg) return HistoryMessage::selectedText(selection); - QString result, original = HistoryMessage::selectedText(selection); - result.reserve(lang(lng_in_reply_to).size() + replyToMsg->from()->name.size() + 4 + original.size()); - result.append('[').append(lang(lng_in_reply_to)).append(' ').append(replyToMsg->from()->name).append(qsl("]\n")).append(original); - return result; -} - -void HistoryReply::initDimensions() { - replyToNameUpdated(); - HistoryMessage::initDimensions(); - if (!_media) { - int32 replyw = st::msgPadding.left() + _maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right(); - if (replyToVia()) { - replyw += st::msgServiceFont->spacew + replyToVia()->maxWidth; - } - if (replyw > _maxw) _maxw = replyw; - } -} - -bool HistoryReply::updateReplyTo(bool force) { - if (replyToMsg || !replyToMsgId) return true; - replyToMsg = App::histItemById(channelId(), replyToMsgId); - - if (replyToMsg) { - App::historyRegReply(this, replyToMsg); - replyToText.setText(st::msgFont, replyToMsg->inReplyText(), _textDlgOptions); - - replyToNameUpdated(); - - replyToLnk = TextLinkPtr(new MessageLink(replyToMsg->history()->peer->id, replyToMsg->id)); - if (!replyToMsg->toHistoryForwarded()) { - if (UserData *bot = replyToMsg->viaBot()) { - _replyToVia = new HistoryMessageVia(peerToUser(bot->id)); - } - } - } else if (force) { - replyToMsgId = 0; - } - if (force) { - initDimensions(); - Notify::historyItemResized(this); - } - return (replyToMsg || !replyToMsgId); -} - -void HistoryReply::replyToNameUpdated() const { - if (replyToMsg) { - QString name = (replyToVia() && replyToMsg->from()->isUser()) ? replyToMsg->from()->asUser()->firstName : App::peerName(replyToMsg->from()); - replyToName.setText(st::msgServiceNameFont, name, _textNameOptions); - replyToVersion = replyToMsg->from()->nameVersion; - bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; - int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; - int32 w = replyToName.maxWidth(); - if (replyToVia()) { - w += st::msgServiceFont->spacew + replyToVia()->maxWidth; - } - - _maxReplyWidth = previewSkip + qMax(w, qMin(replyToText.maxWidth(), 4 * w)); - } else { - _maxReplyWidth = st::msgDateFont->width(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message)); - } - _maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + _maxReplyWidth + st::msgReplyPadding.right(); -} - -int32 HistoryReply::replyToWidth() const { - return _maxReplyWidth; -} - -TextLinkPtr HistoryReply::replyToLink() const { - return replyToLnk; -} - -MsgId HistoryReply::replyToId() const { - return replyToMsgId; -} - -HistoryItem *HistoryReply::replyToMessage() const { - return replyToMsg; -} - -void HistoryReply::replyToReplaced(HistoryItem *oldItem, HistoryItem *newItem) { - if (replyToMsg == oldItem) { - replyToMsg = newItem; - if (!newItem) { - replyToMsgId = 0; - initDimensions(); - } - } -} - -void HistoryReply::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { - if (replyToMsg && replyToMsg->from()->nameVersion > replyToVersion) { - replyToNameUpdated(); - } - HistoryMessage::draw(p, r, selection, ms); -} - -void HistoryReply::drawReplyTo(Painter &p, int32 x, int32 y, int32 w, bool selected, bool likeService) const { - style::color bar; - bool outbg = out() && !fromChannel(); - if (likeService) { - bar = st::white; - } else { - bar = (selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor)); - } - QRect rbar(rtlrect(x + st::msgReplyBarPos.x(), y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), w + 2 * x)); - p.fillRect(rbar, bar); - - if (w > st::msgReplyBarSkip) { - if (replyToMsg) { - bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; - int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; - - if (hasPreview) { - ImagePtr replyPreview = replyToMsg->getMedia()->replyPreview(); - if (!replyPreview->isNull()) { - QRect to(rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x)); - p.drawPixmap(to.x(), to.y(), replyPreview->pixSingle(replyPreview->width() / cIntRetinaFactor(), replyPreview->height() / cIntRetinaFactor(), to.width(), to.height())); - if (selected) { - App::roundRect(p, to, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); - } - } - } - if (w > st::msgReplyBarSkip + previewSkip) { - if (likeService) { - p.setPen(st::white); - } else { - p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); - } - replyToName.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x); - if (replyToVia() && w > st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew) { - p.setFont(st::msgServiceFont); - p.drawText(x + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, replyToVia()->text); - } - - HistoryMessage *replyToAsMsg = replyToMsg->toHistoryMessage(); - if (likeService) { - } else if ((replyToAsMsg && replyToAsMsg->emptyText()) || replyToMsg->serviceMsg()) { - style::color date(outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg)); - p.setPen(date); - } else { - p.setPen(st::msgColor); - } - replyToText.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top() + st::msgServiceNameFont->height, w - st::msgReplyBarSkip - previewSkip, w + 2 * x); - } - } else { - p.setFont(st::msgDateFont); - style::color date(outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg)); - p.setPen(likeService ? st::white : date); - p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message), w - st::msgReplyBarSkip)); - } - } -} - -void HistoryReply::drawMessageText(Painter &p, QRect trect, uint32 selection) const { - int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - - drawReplyTo(p, trect.x(), trect.y(), trect.width(), (selection == FullSelection)); - - trect.setY(trect.y() + h); - HistoryMessage::drawMessageText(p, trect, selection); -} - -int32 HistoryReply::resize(int32 width) { - HistoryMessage::resize(width); - - if (drawBubble()) { - if (emptyText() && !displayFromName() && !via()) { - _height += st::msgPadding.top() + st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom() + st::mediaHeaderSkip; - } else { - _height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } - if (replyToVia()) { - bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; - int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; - replyToVia()->resize(width - st::msgPadding.left() - st::msgPadding.right() - st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew); - } - } - return _height; -} - -void HistoryReply::resizeVia(int32 w) const { - if (!replyToVia()) return; - - bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; - int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; - replyToVia()->resize(w - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew); -} - -bool HistoryReply::hasPoint(int32 x, int32 y) const { - if (drawBubble()) { - int32 left = 0, width = 0; - countPositionAndSize(left, width); - if (width < 1) return false; - - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); - return r.contains(x, y); - } - return HistoryMessage::hasPoint(x, y); -} - -void HistoryReply::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { - lnk = TextLinkPtr(); - state = HistoryDefaultCursorState; - - if (drawBubble()) { - int32 left = 0, width = 0; - countPositionAndSize(left, width); - if (displayFromPhoto()) { // from user left photo - if (x >= left - st::msgPhotoSkip && x < left - st::msgPhotoSkip + st::msgPhotoSize) { - return HistoryMessage::getState(lnk, state, x, y); - } - } - if (width < 1) return; - - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); - if (displayFromName()) { - style::font nameFont(st::msgNameFont); - if (y >= r.top() + st::msgPadding.top() && y < r.top() + st::msgPadding.top() + nameFont->height) { - return HistoryMessage::getState(lnk, state, x, y); - } - r.setTop(r.top() + nameFont->height); - } - QRect trect(r.marginsAdded(-st::msgPadding)); - - int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - if (y >= trect.top() && y < trect.top() + h) { - if (replyToMsg && y >= trect.top() + st::msgReplyPadding.top() && y < trect.top() + st::msgReplyPadding.top() + st::msgReplyBarSize.height() && x >= trect.left() && x < trect.right()) { - lnk = replyToLnk; - } - return; - } - y -= h; - } - return HistoryMessage::getState(lnk, state, x, y); -} - -void HistoryReply::getStateFromMessageText(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const QRect &r) const { - int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - - QRect realr(r); - realr.setHeight(r.height() - h); - HistoryMessage::getStateFromMessageText(lnk, state, x, y, realr); -} - -void HistoryReply::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const { - symbol = 0; - after = false; - upon = false; - - if (drawBubble()) { - int32 left = 0, width = 0; - countPositionAndSize(left, width); - if (width < 1) return; - - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); - if (displayFromName()) { - style::font nameFont(st::msgNameFont); - if (y >= r.top() + st::msgPadding.top() && y < r.top() + st::msgPadding.top() + nameFont->height) { - return HistoryMessage::getSymbol(symbol, after, upon, x, y); - } - r.setTop(r.top() + nameFont->height); - } - QRect trect(r.marginsAdded(-st::msgPadding)); - - int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - y -= h; - } - return HistoryMessage::getSymbol(symbol, after, upon, x, y); -} - -HistoryReply::~HistoryReply() { - if (replyToMsg) { - App::historyUnregReply(this, replyToMsg); - } else if (replyToMsgId && App::api()) { - App::api()->itemRemoved(this); - } - deleteAndMark(_replyToVia); -} - -void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { - QList links; +void HistoryService::setMessageByAction(const MTPmessageAction &action) { + QList links; LangString text = lang(lng_message_empty); QString from = textcmdLink(1, _from->name); @@ -7310,7 +7863,7 @@ void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { if (u == _from) { text = lng_action_user_joined(lt_from, from); } else { - links.push_back(TextLinkPtr(new PeerLink(u))); + links.push_back(MakeShared(u)); text = lng_action_add_user(lt_from, from, lt_user, textcmdLink(2, u->name)); } } else if (v.isEmpty()) { @@ -7326,14 +7879,11 @@ void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { } else { text = lng_action_add_users_and_last(lt_accumulated, text, lt_user, linkText); } - links.push_back(TextLinkPtr(new PeerLink(u))); + links.push_back(MakeShared(u)); } text = lng_action_add_users_many(lt_from, from, lt_users, text); } if (foundSelf) { - if (unread() && history()->peer->isChat() && !history()->peer->asChat()->inviterForSpamReport && _from->isUser()) { - history()->peer->asChat()->inviterForSpamReport = peerToUser(_from->id); - } if (history()->peer->isMegagroup()) { history()->peer->asChannel()->mgInfo->joinedMessageFound = true; } @@ -7342,13 +7892,13 @@ void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { case mtpc_messageActionChatJoinedByLink: { const MTPDmessageActionChatJoinedByLink &d(action.c_messageActionChatJoinedByLink()); - if (true || peerFromUser(d.vinviter_id) == _from->id) { + //if (true || peerFromUser(d.vinviter_id) == _from->id) { text = lng_action_user_joined_by_link(lt_from, from); //} else { - //UserData *u = App::user(App::peerFromUser(d.vinviter_id)); - //second = TextLinkPtr(new PeerLink(u)); - //text = lng_action_user_joined_by_link_from(lt_from, from, lt_inviter, textcmdLink(2, u->name)); - } + // UserData *u = App::user(App::peerFromUser(d.vinviter_id)); + // links.push_back(MakeShared(u)); + // text = lng_action_user_joined_by_link_from(lt_from, from, lt_inviter, textcmdLink(2, u->name)); + //} if (_from->isSelf() && history()->peer->isMegagroup()) { history()->peer->asChannel()->mgInfo->joinedMessageFound = true; } @@ -7357,16 +7907,11 @@ void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { case mtpc_messageActionChatCreate: { const MTPDmessageActionChatCreate &d(action.c_messageActionChatCreate()); text = lng_action_created_chat(lt_from, from, lt_title, textClean(qs(d.vtitle))); - if (unread()) { - if (history()->peer->isChat() && !history()->peer->asChat()->inviterForSpamReport && _from->isUser() && peerToUser(_from->id) != MTP::authedId()) { - history()->peer->asChat()->inviterForSpamReport = peerToUser(_from->id); - } - } } break; case mtpc_messageActionChannelCreate: { const MTPDmessageActionChannelCreate &d(action.c_messageActionChannelCreate()); - if (fromChannel()) { + if (isPost()) { text = lng_action_created_channel(lt_title, textClean(qs(d.vtitle))); } else { text = lng_action_created_chat(lt_from, from, lt_title, textClean(qs(d.vtitle))); @@ -7374,7 +7919,7 @@ void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { } break; case mtpc_messageActionChatDeletePhoto: { - text = fromChannel() ? lang(lng_action_removed_photo_channel) : lng_action_removed_photo(lt_from, from); + text = isPost() ? lang(lng_action_removed_photo_channel) : lng_action_removed_photo(lt_from, from); } break; case mtpc_messageActionChatDeleteUser: { @@ -7383,7 +7928,7 @@ void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { text = lng_action_user_left(lt_from, from); } else { UserData *u = App::user(peerFromUser(d.vuser_id)); - links.push_back(TextLinkPtr(new PeerLink(u))); + links.push_back(MakeShared(u)); text = lng_action_kick_user(lt_from, from, lt_user, textcmdLink(2, u->name)); } } break; @@ -7391,20 +7936,20 @@ void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { case mtpc_messageActionChatEditPhoto: { const MTPDmessageActionChatEditPhoto &d(action.c_messageActionChatEditPhoto()); if (d.vphoto.type() == mtpc_photo) { - _media = new HistoryPhoto(history()->peer, d.vphoto.c_photo(), st::msgServicePhotoWidth); + _media.reset(this, new HistoryPhoto(history()->peer, d.vphoto.c_photo(), st::msgServicePhotoWidth)); } - text = fromChannel() ? lang(lng_action_changed_photo_channel) : lng_action_changed_photo(lt_from, from); + text = isPost() ? lang(lng_action_changed_photo_channel) : lng_action_changed_photo(lt_from, from); } break; case mtpc_messageActionChatEditTitle: { const MTPDmessageActionChatEditTitle &d(action.c_messageActionChatEditTitle()); - text = fromChannel() ? lng_action_changed_title_channel(lt_title, textClean(qs(d.vtitle))) : lng_action_changed_title(lt_from, from, lt_title, textClean(qs(d.vtitle))); + text = isPost() ? lng_action_changed_title_channel(lt_title, textClean(qs(d.vtitle))) : lng_action_changed_title(lt_from, from, lt_title, textClean(qs(d.vtitle))); } break; case mtpc_messageActionChatMigrateTo: { - _flags |= MTPDmessage_flag_IS_GROUP_MIGRATE; + _flags |= MTPDmessage_ClientFlag::f_is_group_migrate; const MTPDmessageActionChatMigrateTo &d(action.c_messageActionChatMigrateTo()); - if (true/*PeerData *channel = App::peerLoaded(peerFromChannel(d.vchannel_id))*/) { + if (true/*PeerData *channel = App::channelLoaded(d.vchannel_id.v)*/) { text = lang(lng_action_group_migrate); } else { text = lang(lng_contacts_loading); @@ -7412,15 +7957,24 @@ void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { } break; case mtpc_messageActionChannelMigrateFrom: { - _flags |= MTPDmessage_flag_IS_GROUP_MIGRATE; + _flags |= MTPDmessage_ClientFlag::f_is_group_migrate; const MTPDmessageActionChannelMigrateFrom &d(action.c_messageActionChannelMigrateFrom()); - if (true/*PeerData *chat = App::peerLoaded(peerFromChannel(d.vchat_id))*/) { + if (true/*PeerData *chat = App::chatLoaded(d.vchat_id.v)*/) { text = lang(lng_action_group_migrate); } else { text = lang(lng_contacts_loading); } } break; + case mtpc_messageActionPinMessage: { + if (updatePinnedText(&from, &text)) { + auto *pinned = Get(); + t_assert(pinned != nullptr); + + links.push_back(pinned->lnk); + } + } break; + default: from = QString(); break; } @@ -7428,84 +7982,220 @@ void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { _text.setText(st::msgServiceFont, text, _historySrvOptions); textstyleRestore(); if (!from.isEmpty()) { - _text.setLink(1, TextLinkPtr(new PeerLink(_from))); + _text.setLink(1, MakeShared(_from)); } for (int32 i = 0, l = links.size(); i < l; ++i) { _text.setLink(i + 2, links.at(i)); } } -HistoryServiceMsg::HistoryServiceMsg(History *history, HistoryBlock *block, const MTPDmessageService &msg) : - HistoryItem(history, block, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) -, _text(st::msgMinWidth) -, _media(0) -{ +bool HistoryService::updatePinned(bool force) { + auto *pinned = Get(); + t_assert(pinned != nullptr); + + if (!force) { + if (!pinned->msgId || pinned->msg) { + return true; + } + } + + if (!pinned->lnk) { + pinned->lnk.reset(new GoToMessageClickHandler(history()->peer->id, pinned->msgId)); + } + bool gotDependencyItem = false; + if (!pinned->msg) { + pinned->msg = App::histItemById(channelId(), pinned->msgId); + if (pinned->msg) { + App::historyRegDependency(this, pinned->msg); + gotDependencyItem = true; + } + } + if (pinned->msg) { + updatePinnedText(); + } else if (force) { + if (pinned->msgId > 0) { + pinned->msgId = 0; + gotDependencyItem = true; + } + updatePinnedText(); + } + if (force) { + setPendingInitDimensions(); + if (gotDependencyItem && App::wnd()) { + App::wnd()->notifySettingGot(); + } + } + return (pinned->msg || !pinned->msgId); +} + +bool HistoryService::updatePinnedText(const QString *pfrom, QString *ptext) { + bool result = false; + QString from, text; + if (pfrom) { + from = *pfrom; + } else { + from = textcmdLink(1, _from->name); + } + + ClickHandlerPtr second; + auto *pinned = Get(); + if (pinned && pinned->msg) { + HistoryMedia *media = pinned->msg->getMedia(); + QString mediaText; + switch (media ? media->type() : MediaTypeCount) { + case MediaTypePhoto: mediaText = lang(lng_action_pinned_media_photo); break; + case MediaTypeVideo: mediaText = lang(lng_action_pinned_media_video); break; + case MediaTypeContact: mediaText = lang(lng_action_pinned_media_contact); break; + case MediaTypeFile: mediaText = lang(lng_action_pinned_media_file); break; + case MediaTypeGif: mediaText = lang(lng_action_pinned_media_gif); break; + case MediaTypeSticker: mediaText = lang(lng_action_pinned_media_sticker); break; + case MediaTypeLocation: mediaText = lang(lng_action_pinned_media_location); break; + case MediaTypeMusicFile: mediaText = lang(lng_action_pinned_media_audio); break; + case MediaTypeVoiceFile: mediaText = lang(lng_action_pinned_media_voice); break; + } + if (mediaText.isEmpty()) { + QString original = pinned->msg->originalText(); + int32 cutat = 0, limit = PinnedMessageTextLimit, size = original.size(); + for (; limit > 0;) { + --limit; + if (cutat >= size) break; + if (original.at(cutat).isLowSurrogate() && cutat + 1 < size && original.at(cutat + 1).isHighSurrogate()) { + cutat += 2; + } else { + ++cutat; + } + } + if (!limit && cutat + 5 < size) { + original = original.mid(0, cutat) + qstr("..."); + } + text = lng_action_pinned_message(lt_from, from, lt_text, textcmdLink(2, original)); + } else { + text = lng_action_pinned_media(lt_from, from, lt_media, textcmdLink(2, mediaText)); + } + second = pinned->lnk; + result = true; + } else if (pinned && pinned->msgId) { + text = lng_action_pinned_media(lt_from, from, lt_media, textcmdLink(2, lang(lng_contacts_loading))); + second = pinned->lnk; + result = true; + } else { + text = lng_action_pinned_media(lt_from, from, lt_media, lang(lng_deleted_message)); + } + if (ptext) { + *ptext = text; + } else { + setServiceText(text); + _text.setLink(1, MakeShared(_from)); + if (second) { + _text.setLink(2, second); + } + if (history()->textCachedFor == this) { + history()->textCachedFor = 0; + } + if (App::main()) { + App::main()->dlgUpdated(history(), id); + } + App::historyUpdateDependent(this); + } + return result; +} + +HistoryService::HistoryService(History *history, const MTPDmessageService &msg) : + HistoryItem(history, msg.vid.v, mtpCastFlags(msg.vflags.v), ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) { + if (msg.has_reply_to_msg_id()) { + UpdateComponents(HistoryServicePinned::Bit()); + MsgId pinnedMsgId = Get()->msgId = msg.vreply_to_msg_id.v; + if (!updatePinned() && App::api()) { + App::api()->requestMessageData(history->peer->asChannel(), pinnedMsgId, new HistoryDependentItemCallback(fullId())); + } + } setMessageByAction(msg.vaction); } -HistoryServiceMsg::HistoryServiceMsg(History *history, HistoryBlock *block, MsgId msgId, QDateTime date, const QString &msg, int32 flags, HistoryMedia *media, int32 from) : - HistoryItem(history, block, msgId, flags, date, from) -, _text(st::msgServiceFont, msg, _historySrvOptions, st::dlgMinWidth) -, _media(media) -{ +HistoryService::HistoryService(History *history, MsgId msgId, QDateTime date, const QString &msg, MTPDmessage::Flags flags, int32 from) : + HistoryItem(history, msgId, flags, date, from) { + _text.setText(st::msgServiceFont, msg, _historySrvOptions); } -void HistoryServiceMsg::initDimensions() { +void HistoryService::initDimensions() { _maxw = _text.maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right(); _minh = _text.minHeight(); if (_media) _media->initDimensions(this); } -QString HistoryServiceMsg::selectedText(uint32 selection) const { +void HistoryService::countPositionAndSize(int32 &left, int32 &width) const { + left = st::msgServiceMargin.left(); + int32 maxwidth = _history->width; + if (Adaptive::Wide()) { + maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); + } + width = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left(); +} + +QString HistoryService::selectedText(uint32 selection) const { uint16 selectedFrom = (selection == FullSelection) ? 0 : (selection >> 16) & 0xFFFF; uint16 selectedTo = (selection == FullSelection) ? 0xFFFF : (selection & 0xFFFF); return _text.original(selectedFrom, selectedTo); } -QString HistoryServiceMsg::inDialogsText() const { +QString HistoryService::inDialogsText() const { return _text.original(0, 0xFFFF, Text::ExpandLinksNone); } -QString HistoryServiceMsg::inReplyText() const { - QString result = HistoryServiceMsg::inDialogsText(); - return result.trimmed().startsWith(from()->name) ? result.trimmed().mid(from()->name.size()).trimmed() : result; +QString HistoryService::inReplyText() const { + QString result = HistoryService::inDialogsText(); + return result.trimmed().startsWith(author()->name) ? result.trimmed().mid(author()->name.size()).trimmed() : result; } -void HistoryServiceMsg::setServiceText(const QString &text) { +void HistoryService::setServiceText(const QString &text) { textstyleSet(&st::serviceTextStyle); _text.setText(st::msgServiceFont, text, _historySrvOptions); textstyleRestore(); initDimensions(); } -void HistoryServiceMsg::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { +void HistoryService::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { + int left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins + countPositionAndSize(left, width); + if (width < 1) return; + + int dateh = 0, unreadbarh = 0; + if (auto *date = Get()) { + dateh = date->height(); + date->paint(p, 0, _history->width); + p.translate(0, dateh); + height -= dateh; + } + if (auto *unreadbar = Get()) { + unreadbarh = unreadbar->height(); + unreadbar->paint(p, 0, _history->width); + p.translate(0, unreadbarh); + height -= unreadbarh; + } + uint64 animms = App::main() ? App::main()->animActiveTimeStart(this) : 0; if (animms > 0 && animms <= ms) { animms = ms - animms; if (animms > st::activeFadeInDuration + st::activeFadeOutDuration) { App::main()->stopAnimActive(); } else { + int skiph = st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); + textstyleSet(&st::inTextStyle); float64 dt = (animms > st::activeFadeInDuration) ? (1 - (animms - st::activeFadeInDuration) / float64(st::activeFadeOutDuration)) : (animms / float64(st::activeFadeInDuration)); float64 o = p.opacity(); p.setOpacity(o * dt); - p.fillRect(0, 0, _history->width, _height, textstyleCurrent()->selectOverlay->b); + p.fillRect(0, skiph, _history->width, _height - skiph, textstyleCurrent()->selectOverlay->b); p.setOpacity(o); } } - textstyleSet(&st::serviceTextStyle); - - int32 left = st::msgServiceMargin.left(), width = _history->width - st::msgServiceMargin.left() - st::msgServiceMargin.left(), height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins - if (width < 1) return; - if (_media) { height -= st::msgServiceMargin.top() + _media->height(); - p.save(); int32 left = st::msgServiceMargin.left() + (width - _media->maxWidth()) / 2, top = st::msgServiceMargin.top() + height + st::msgServiceMargin.top(); p.translate(left, top); _media->draw(p, this, r.translated(-left, -top), selection == FullSelection, ms); - p.restore(); + p.translate(-left, -top); } QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); @@ -7516,16 +8206,28 @@ void HistoryServiceMsg::draw(Painter &p, const QRect &r, uint32 selection, uint6 } App::roundRect(p, left, st::msgServiceMargin.top(), width, height, App::msgServiceBg(), (selection == FullSelection) ? ServiceSelectedCorners : ServiceCorners); + textstyleSet(&st::serviceTextStyle); + p.setBrush(Qt::NoBrush); - p.setPen(st::msgServiceColor->p); - p.setFont(st::msgServiceFont->f); + p.setPen(st::msgServiceColor); + p.setFont(st::msgServiceFont); uint16 selectedFrom = (selection == FullSelection) ? 0 : (selection >> 16) & 0xFFFF; uint16 selectedTo = (selection == FullSelection) ? 0 : selection & 0xFFFF; _text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, selectedFrom, selectedTo); + textstyleRestore(); + + if (int skiph = dateh + unreadbarh) { + p.translate(0, -skiph); + } } -int32 HistoryServiceMsg::resize(int32 width) { +int32 HistoryService::resizeGetHeight_(int32 width) { + int32 maxwidth = _history->width; + if (Adaptive::Wide()) { + maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); + } + if (width > maxwidth) width = maxwidth; width -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins if (width < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) width = st::msgServicePadding.left() + st::msgServicePadding.right() + 1; @@ -7543,28 +8245,54 @@ int32 HistoryServiceMsg::resize(int32 width) { } _height += st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom(); if (_media) { - _height += st::msgServiceMargin.top() + _media->resize(_media->currentWidth(), this); + _height += st::msgServiceMargin.top() + _media->resizeGetHeight(_media->currentWidth(), this); + } + _height += displayedDateHeight(); + if (auto *unreadbar = Get()) { + _height += unreadbar->height(); } return _height; } -bool HistoryServiceMsg::hasPoint(int32 x, int32 y) const { - int32 left = st::msgServiceMargin.left(), width = _history->width - st::msgServiceMargin.left() - st::msgServiceMargin.left(), height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins +bool HistoryService::hasPoint(int32 x, int32 y) const { + int left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins + countPositionAndSize(left, width); if (width < 1) return false; + if (int dateh = displayedDateHeight()) { + y -= dateh; + height -= dateh; + } + if (auto *unreadbar = Get()) { + int unreadbarh = unreadbar->height(); + y -= unreadbarh; + height -= unreadbarh; + } + if (_media) { height -= st::msgServiceMargin.top() + _media->height(); } return QRect(left, st::msgServiceMargin.top(), width, height).contains(x, y); } -void HistoryServiceMsg::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { - lnk = TextLinkPtr(); +void HistoryService::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { + lnk.clear(); state = HistoryDefaultCursorState; - int32 left = st::msgServiceMargin.left(), width = _history->width - st::msgServiceMargin.left() - st::msgServiceMargin.left(), height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins + int left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins + countPositionAndSize(left, width); if (width < 1) return; + if (int dateh = displayedDateHeight()) { + y -= dateh; + height -= dateh; + } + if (auto *unreadbar = Get()) { + int unreadbarh = unreadbar->height(); + y -= unreadbarh; + height -= unreadbarh; + } + if (_media) { height -= st::msgServiceMargin.top() + _media->height(); } @@ -7580,14 +8308,25 @@ void HistoryServiceMsg::getState(TextLinkPtr &lnk, HistoryCursorState &state, in } } -void HistoryServiceMsg::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const { +void HistoryService::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const { symbol = 0; after = false; upon = false; - int32 left = st::msgServiceMargin.left(), width = _history->width - st::msgServiceMargin.left() - st::msgServiceMargin.left(), height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins + int left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins + countPositionAndSize(left, width); if (width < 1) return; + if (int dateh = displayedDateHeight()) { + y -= dateh; + height -= dateh; + } + if (auto *unreadbar = Get()) { + int unreadbarh = unreadbar->height(); + y -= unreadbarh; + height -= unreadbarh; + } + if (_media) { height -= st::msgServiceMargin.top() + _media->height(); } @@ -7597,7 +8336,7 @@ void HistoryServiceMsg::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 textstyleRestore(); } -void HistoryServiceMsg::drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const { +void HistoryService::drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const { if (cacheFor != this) { cacheFor = this; cache.setText(st::dlgHistFont, inDialogsText(), _textDlgOptions); @@ -7607,50 +8346,47 @@ void HistoryServiceMsg::drawInDialog(Painter &p, const QRect &r, bool act, const cache.drawElided(p, tr.left(), tr.top(), tr.width(), tr.height() / st::dlgHistFont->height); } -QString HistoryServiceMsg::notificationText() const { +QString HistoryService::notificationText() const { QString msg = _text.original(); - if (msg.size() > 0xFF) msg = msg.mid(0, 0xFF) + qsl(".."); + if (msg.size() > 0xFF) msg = msg.mid(0, 0xFF) + qsl("..."); return msg; } -HistoryMedia *HistoryServiceMsg::getMedia(bool inOverview) const { - return inOverview ? 0 : _media; +HistoryMedia *HistoryService::getMedia() const { + return _media.data(); } -HistoryServiceMsg::~HistoryServiceMsg() { - delete _media; -} - -HistoryDateMsg::HistoryDateMsg(History *history, HistoryBlock *block, const QDate &date) : -HistoryServiceMsg(history, block, clientMsgId(), QDateTime(date), langDayOfMonthFull(date)) { -} - -void HistoryDateMsg::setDate(const QDateTime &date) { - if (this->date.date() != date.date()) { - setServiceText(langDayOfMonthFull(date.date())); +HistoryService::~HistoryService() { + if (auto pinned = Get()) { + if (pinned->msg) { + App::historyUnregDependency(this, pinned->msg); + } } - HistoryServiceMsg::setDate(date); + _media.clear(this); } -HistoryItem *createDayServiceMsg(History *history, HistoryBlock *block, QDateTime date) { - return regItem(new HistoryDateMsg(history, block, date.date())); +HistoryGroup::HistoryGroup(History *history, const MTPDmessageGroup &group, const QDateTime &date) + : HistoryService(history, clientMsgId(), date, lng_channel_comments_count(lt_count, group.vcount.v)/* + qsl(" (%1 ... %2)").arg(group.vmin_id.v).arg(group.vmax_id.v)*/) + , _minId(group.vmin_id.v) + , _maxId(group.vmax_id.v) + , _count(group.vcount.v) + , _lnk(new CommentsClickHandler(this)) { } -HistoryGroup::HistoryGroup(History *history, HistoryBlock *block, const MTPDmessageGroup &group, const QDateTime &date) : -HistoryServiceMsg(history, block, clientMsgId(), date, lng_channel_comments_count(lt_count, group.vcount.v)/* + qsl(" (%1 .. %2)").arg(group.vmin_id.v).arg(group.vmax_id.v)*/), -_minId(group.vmin_id.v), _maxId(group.vmax_id.v), _count(group.vcount.v), _lnk(new CommentsLink(this)) { +HistoryGroup::HistoryGroup(History *history, HistoryItem *newItem, const QDateTime &date) + : HistoryService(history, clientMsgId(), date, lng_channel_comments_count(lt_count, 1)/* + qsl(" (%1 ... %2)").arg(newItem->id - 1).arg(newItem->id + 1)*/) + , _minId(newItem->id - 1) + , _maxId(newItem->id + 1) + , _count(1) + , _lnk(new CommentsClickHandler(this)) { } -HistoryGroup::HistoryGroup(History *history, HistoryBlock *block, HistoryItem *newItem, const QDateTime &date) : -HistoryServiceMsg(history, block, clientMsgId(), date, lng_channel_comments_count(lt_count, 1)/* + qsl(" (%1 .. %2)").arg(newItem->id - 1).arg(newItem->id + 1)*/), -_minId(newItem->id - 1), _maxId(newItem->id + 1), _count(1), _lnk(new CommentsLink(this)) { -} - -void HistoryGroup::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { - lnk = TextLinkPtr(); +void HistoryGroup::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { + lnk.clear(); state = HistoryDefaultCursorState; - int32 left = st::msgServiceMargin.left(), width = _history->width - st::msgServiceMargin.left() - st::msgServiceMargin.left(), height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins + int32 left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins + countPositionAndSize(left, width); if (width < 1) return; QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); @@ -7704,67 +8440,46 @@ bool HistoryGroup::decrementCount() { } void HistoryGroup::updateText() { - setServiceText(lng_channel_comments_count(lt_count, _count)/* + qsl(" (%1 .. %2)").arg(_minId).arg(_maxId)*/); + setServiceText(lng_channel_comments_count(lt_count, _count)/* + qsl(" (%1 ... %2)").arg(_minId).arg(_maxId)*/); } -HistoryCollapse::HistoryCollapse(History *history, HistoryBlock *block, MsgId wasMinId, const QDateTime &date) : -HistoryServiceMsg(history, block, clientMsgId(), date, qsl("-")), -_wasMinId(wasMinId) { +HistoryCollapse::HistoryCollapse(History *history, MsgId wasMinId, const QDateTime &date) + : HistoryService(history, clientMsgId(), date, qsl("-")) + , _wasMinId(wasMinId) { } void HistoryCollapse::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { } -void HistoryCollapse::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { - lnk = TextLinkPtr(); +void HistoryCollapse::getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { + lnk.clear(); state = HistoryDefaultCursorState; } -HistoryJoined::HistoryJoined(History *history, HistoryBlock *block, const QDateTime &inviteDate, UserData *inviter, int32 flags) : -HistoryServiceMsg(history, block, clientMsgId(), inviteDate, QString(), flags) { +HistoryJoined::HistoryJoined(History *history, const QDateTime &inviteDate, UserData *inviter, MTPDmessage::Flags flags) + : HistoryService(history, clientMsgId(), inviteDate, QString(), flags) { textstyleSet(&st::serviceTextStyle); if (peerToUser(inviter->id) == MTP::authedId()) { _text.setText(st::msgServiceFont, lang(history->isMegagroup() ? lng_action_you_joined_group : lng_action_you_joined), _historySrvOptions); } else { _text.setText(st::msgServiceFont, history->isMegagroup() ? lng_action_add_you_group(lt_from, textcmdLink(1, inviter->name)) : lng_action_add_you(lt_from, textcmdLink(1, inviter->name)), _historySrvOptions); - _text.setLink(1, TextLinkPtr(new PeerLink(inviter))); + _text.setLink(1, MakeShared(inviter)); } textstyleRestore(); } -HistoryUnreadBar::HistoryUnreadBar(History *history, HistoryBlock *block, int32 count, const QDateTime &date) : HistoryItem(history, block, clientMsgId(), 0, date, 0), freezed(false) { - setCount(count); - initDimensions(); +void GoToMessageClickHandler::onClickImpl() const { + if (App::main()) { + HistoryItem *current = App::mousedItem(); + if (current && current->history()->peer->id == peer()) { + App::main()->pushReplyReturn(current); + } + Ui::showPeerHistory(peer(), msgid()); + } } -void HistoryUnreadBar::initDimensions() { - _maxw = st::msgPadding.left() + st::msgPadding.right() + 1; - _minh = st::unreadBarHeight; +void CommentsClickHandler::onClickImpl() const { + if (App::main() && peerIsChannel(peer())) { + Ui::showPeerHistory(peer(), msgid()); + } } - -void HistoryUnreadBar::setCount(int32 count) { - if (!count) freezed = true; - if (freezed) return; - text = lng_unread_bar(lt_count, count); -} - -void HistoryUnreadBar::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { - p.fillRect(0, st::lineWidth, _history->width, st::unreadBarHeight - 2 * st::lineWidth, st::unreadBarBG->b); - p.fillRect(0, st::unreadBarHeight - st::lineWidth, _history->width, st::lineWidth, st::unreadBarBorder->b); - p.setFont(st::unreadBarFont->f); - p.setPen(st::unreadBarColor->p); - p.drawText(QRect(0, 0, _history->width, st::unreadBarHeight - st::lineWidth), text, style::al_center); -} - -int32 HistoryUnreadBar::resize(int32 width) { - _height = st::unreadBarHeight; - return _height; -} - -void HistoryUnreadBar::drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const { -} - -QString HistoryUnreadBar::notificationText() const { - return QString(); -} - diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index b6a3821696..2f9df55a5f 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -16,7 +16,7 @@ In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once @@ -40,7 +40,7 @@ public: typedef QHash Map; Map map; - Histories() : _a_typings(animation(this, &Histories::step_typings)), unreadFull(0), unreadMuted(0) { + Histories() : _a_typings(animation(this, &Histories::step_typings)), _unreadFull(0), _unreadMuted(0) { } void regSendAction(History *history, UserData *user, const MTPSendMessageAction &action); @@ -52,17 +52,38 @@ public: void clear(); void remove(const PeerId &peer); ~Histories() { - unreadFull = unreadMuted = 0; + _unreadFull = _unreadMuted = 0; } HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type); - // HistoryItem *addToBack(const MTPgeoChatMessage &msg, bool newMsg = true); typedef QMap TypingHistories; // when typing in this history started TypingHistories typing; Animation _a_typings; - int32 unreadFull, unreadMuted; + int32 unreadBadge() const { + return _unreadFull - (cIncludeMuted() ? 0 : _unreadMuted); + } + bool unreadOnlyMuted() const { + return cIncludeMuted() ? (_unreadMuted >= _unreadFull) : false; + } + void unreadIncrement(int32 count, bool muted) { + _unreadFull += count; + if (muted) { + _unreadMuted += count; + } + } + void unreadMuteChanged(int32 count, bool muted) { + if (muted) { + _unreadMuted += count; + } else { + _unreadMuted -= count; + } + } + +private: + int32 _unreadFull, _unreadMuted; + }; class HistoryBlock; @@ -93,25 +114,25 @@ struct FakeDialogRow { enum HistoryMediaType { MediaTypePhoto, MediaTypeVideo, - MediaTypeGeo, MediaTypeContact, - MediaTypeAudio, - MediaTypeDocument, + MediaTypeFile, MediaTypeGif, MediaTypeSticker, - MediaTypeImageLink, + MediaTypeLocation, MediaTypeWebPage, + MediaTypeMusicFile, + MediaTypeVoiceFile, MediaTypeCount }; enum MediaOverviewType { - OverviewPhotos, - OverviewVideos, - OverviewAudioDocuments, - OverviewDocuments, - OverviewAudios, - OverviewLinks, + OverviewPhotos = 0, + OverviewVideos = 1, + OverviewMusicFiles = 2, + OverviewFiles = 3, + OverviewVoiceFiles = 4, + OverviewLinks = 5, OverviewCount }; @@ -120,9 +141,9 @@ inline MTPMessagesFilter typeToMediaFilter(MediaOverviewType &type) { switch (type) { case OverviewPhotos: return MTP_inputMessagesFilterPhotos(); case OverviewVideos: return MTP_inputMessagesFilterVideo(); - case OverviewAudioDocuments: return MTP_inputMessagesFilterAudioDocuments(); - case OverviewDocuments: return MTP_inputMessagesFilterDocument(); - case OverviewAudios: return MTP_inputMessagesFilterAudio(); + case OverviewMusicFiles: return MTP_inputMessagesFilterMusic(); + case OverviewFiles: return MTP_inputMessagesFilterDocument(); + case OverviewVoiceFiles: return MTP_inputMessagesFilterVoice(); case OverviewLinks: return MTP_inputMessagesFilterUrl(); default: type = OverviewCount; break; } @@ -133,8 +154,8 @@ enum SendActionType { SendActionTyping, SendActionRecordVideo, SendActionUploadVideo, - SendActionRecordAudio, - SendActionUploadAudio, + SendActionRecordVoice, + SendActionUploadVoice, SendActionUploadPhoto, SendActionUploadFile, SendActionChooseLocation, @@ -148,9 +169,44 @@ struct SendAction { int32 progress; }; +struct HistoryDraft { + HistoryDraft() : msgId(0), previewCancelled(false) { + } + HistoryDraft(const QString &text, MsgId msgId, const MessageCursor &cursor, bool previewCancelled) + : text(text) + , msgId(msgId) + , cursor(cursor) + , previewCancelled(previewCancelled) { + } + HistoryDraft(const FlatTextarea &field, MsgId msgId, bool previewCancelled) + : text(field.getLastText()) + , msgId(msgId) + , cursor(field) + , previewCancelled(previewCancelled) { + } + QString text; + MsgId msgId; // replyToId for message draft, editMsgId for edit draft + MessageCursor cursor; + bool previewCancelled; +}; +struct HistoryEditDraft : public HistoryDraft { + HistoryEditDraft() + : HistoryDraft() + , saveRequest(0) { + } + HistoryEditDraft(const QString &text, MsgId msgId, const MessageCursor &cursor, bool previewCancelled, mtpRequestId saveRequest = 0) + : HistoryDraft(text, msgId, cursor, previewCancelled) + , saveRequest(saveRequest) { + } + HistoryEditDraft(const FlatTextarea &field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequest = 0) + : HistoryDraft(field, msgId, previewCancelled) + , saveRequest(saveRequest) { + } + mtpRequestId saveRequest; +}; + class HistoryMedia; class HistoryMessage; -class HistoryUnreadBar; enum AddToOverviewMethod { AddToOverviewNew, // when new message is added to history @@ -158,11 +214,15 @@ enum AddToOverviewMethod { AddToOverviewBack, // when new messages slice was received and it is the last one, we index all media }; +struct DialogsIndexed; class ChannelHistory; class History { public: History(const PeerId &peerId); + History(const History &) = delete; + History &operator=(const History &) = delete; + ChannelId channelId() const { return peerToChannel(peer->id); } @@ -179,25 +239,15 @@ public: return blocks.isEmpty(); } void clear(bool leaveItems = false); - void clearUpto(MsgId msgId); - void blockResized(HistoryBlock *block, int32 dh); - void removeBlock(HistoryBlock *block); - virtual ~History() { - clear(); - } + virtual ~History(); - HistoryItem *createItem(HistoryBlock *block, const MTPMessage &msg, bool applyServiceAction); - HistoryItem *createItemForwarded(HistoryBlock *block, MsgId id, QDateTime date, int32 from, HistoryMessage *msg); - HistoryItem *createItemDocument(HistoryBlock *block, MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption); - HistoryItem *createItemPhoto(HistoryBlock *block, MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption); - - HistoryItem *addNewService(MsgId msgId, QDateTime date, const QString &text, int32 flags = 0, HistoryMedia *media = 0, bool newMsg = true); + HistoryItem *addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true); HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type); HistoryItem *addToHistory(const MTPMessage &msg); - HistoryItem *addNewForwarded(MsgId id, QDateTime date, int32 from, HistoryMessage *item); - HistoryItem *addNewDocument(MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption); - HistoryItem *addNewPhoto(MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption); + HistoryItem *addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *item); + HistoryItem *addNewDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); + HistoryItem *addNewPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); void addOlderSlice(const QVector &slice, const QVector *collapsed); void addNewerSlice(const QVector &slice, const QVector *collapsed); @@ -207,7 +257,7 @@ public: void newItemAdded(HistoryItem *item); void unregTyping(UserData *from); - int32 countUnread(MsgId upTo); + int countUnread(MsgId upTo); void updateShowFrom(); MsgId inboxRead(MsgId upTo); MsgId inboxRead(HistoryItem *wasRead); @@ -216,10 +266,11 @@ public: HistoryItem *lastImportantMessage() const; - void setUnreadCount(int32 newUnreadCount, bool psUpdate = true); + void setUnreadCount(int newUnreadCount, bool psUpdate = true); void setMute(bool newMute); - void getNextShowFrom(HistoryBlock *block, int32 i); + void getNextShowFrom(HistoryBlock *block, int i); void addUnreadBar(); + void destroyUnreadBar(); void clearNotifications(); bool loadedAtBottom() const; // last message is in the list @@ -229,18 +280,35 @@ public: void getReadyFor(MsgId msgId, MsgId &fixInScrollMsgId, int32 &fixInScrollMsgTop); void setLastMessage(HistoryItem *msg); - void setPosInDialogsDate(const QDateTime &date); void fixLastMessage(bool wasAtBottom); + typedef QMap ChatListLinksMap; + void setChatsListDate(const QDateTime &date); + QPair adjustByPosInChatsList(DialogsIndexed &indexed); + uint64 sortKeyInChatList() const { + return _sortKeyInChatList; + } + bool inChatList() const { + return !_chatListLinks.isEmpty(); + } + int32 posInChatList() const { + return mainChatListLink()->pos; + } + DialogRow *addToChatList(DialogsIndexed &indexed); + void removeFromChatList(DialogsIndexed &indexed); + void removeChatListEntryByLetter(QChar letter); + void addChatListEntryByLetter(QChar letter, DialogRow *row); + void updateChatListEntry() const; + MsgId minMsgId() const; MsgId maxMsgId() const; MsgId msgIdForRead() const; - int32 geomResize(int32 newWidth, int32 *ytransform = 0, const HistoryItem *resizedItem = 0); // return new size + int resizeGetHeight(int newWidth); void removeNotification(HistoryItem *item) { if (!notifies.isEmpty()) { - for (NotifyQueue::iterator i = notifies.begin(), e = notifies.end(); i != e; ++i) { + for (auto i = notifies.begin(), e = notifies.end(); i != e; ++i) { if ((*i) == item) { notifies.erase(i); break; @@ -263,17 +331,30 @@ public: if (!notifies.isEmpty() && notifies.back() == item) notifies.pop_back(); } + bool hasPendingResizedItems() const { + return _flags & Flag::f_has_pending_resized_items; + } + void setHasPendingResizedItems(); + void setPendingResize() { + _flags |= Flag::f_pending_resize; + setHasPendingResizedItems(); + } + void paintDialog(Painter &p, int32 w, bool sel) const; - bool updateTyping(uint64 ms = 0, uint32 dots = 0, bool force = false); + bool updateTyping(uint64 ms, bool force = false); void clearLastKeyboard(); + // optimization for userpics displayed on the left + // if this returns false there is no need to even try to handle them + bool canHaveFromPhotos() const; + typedef QList Blocks; Blocks blocks; int32 width, height, msgCount, unreadCount; int32 inboxReadBefore, outboxReadBefore; HistoryItem *showFrom; - HistoryUnreadBar *unreadBar; + HistoryItem *unreadBar; PeerData *peer; bool oldLoaded, newLoaded; @@ -283,12 +364,52 @@ public: typedef QList NotifyQueue; NotifyQueue notifies; - QString draft; - MsgId draftToId; - MessageCursor draftCursor; - bool draftPreviewCancelled; - int32 lastWidth, lastScrollTop; - MsgId lastShowAtMsgId; + HistoryDraft *msgDraft; + HistoryEditDraft *editDraft; + HistoryDraft *draft() { + return editDraft ? editDraft : msgDraft; + } + void setMsgDraft(HistoryDraft *draft) { + if (msgDraft) delete msgDraft; + msgDraft = draft; + } + void setEditDraft(HistoryEditDraft *draft) { + if (editDraft) delete editDraft; + editDraft = draft; + } + + // some fields below are a property of a currently displayed instance of this + // conversation history not a property of the conversation history itself +public: + // we save the last showAtMsgId to restore the state when switching + // between different conversation histories + MsgId showAtMsgId; + + // we save a pointer of the history item at the top of the displayed window + // together with an offset from the window top to the top of this message + // resulting scrollTop = top(scrollTopItem) + scrollTopOffset + HistoryItem *scrollTopItem; + int scrollTopOffset; + void forgetScrollState() { + scrollTopItem = nullptr; + } + + // find the correct scrollTopItem and scrollTopOffset using given top + // of the displayed window relative to the history start coord + void countScrollState(int top); + +protected: + // when this item is destroyed scrollTopItem just points to the next one + // and scrollTopOffset remains the same + // if we are at the bottom of the window scrollTopItem == nullptr and + // scrollTopOffset is undefined + void getNextScrollTopItem(HistoryBlock *block, int32 i); + + // helper method for countScrollState(int top) + void countScrollTopItem(int top); + +public: + bool mute; bool lastKeyboardInited, lastKeyboardUsed; @@ -300,17 +421,13 @@ public: mutable const HistoryItem *textCachedFor; // cache mutable Text lastItemTextCache; - typedef QMap DialogLinks; - DialogLinks dialogs; - uint64 posInDialogs; // like ((unixtime) << 32) | (incremented counter) - typedef QMap TypingUsers; TypingUsers typing; typedef QMap SendActionUsers; SendActionUsers sendActions; QString typingStr; Text typingText; - uint32 typingFrame; + uint32 typingDots; QMap mySendActions; typedef QList MediaOverview; @@ -348,23 +465,97 @@ public: void changeMsgId(MsgId oldId, MsgId newId); +protected: + + void clearOnDestroy(); + HistoryItem *addNewToLastBlock(const MTPMessage &msg, NewMessageType type); + + friend class HistoryBlock; + + // this method just removes a block from the blocks list + // when the last item from this block was detached and + // calls the required previousItemChanged() + void removeBlock(HistoryBlock *block); + + void clearBlocks(bool leaveItems); + + HistoryItem *createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem); + HistoryItem *createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *msg); + HistoryItem *createItemDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); + HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); + + HistoryItem *addNewItem(HistoryItem *adding, bool newMsg); + HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex); + + // All this methods add a new item to the first or last block + // depending on if we are in isBuildingFronBlock() state. + // The last block is created on the go if it is needed. + + // If the previous item is a message group the new group is + // not created but is just united with the previous one. + // create(HistoryItem *previous) should return a new HistoryGroup* + // unite(HistoryGroup *existing) should unite a new group with an existing + template + void addMessageGroup(CreateGroup create, UniteGroup unite); + void addMessageGroup(const MTPDmessageGroup &group); + + // Adds the item to the back or front block, depending on + // isBuildingFrontBlock(), creating the block if necessary. + void addItemToBlock(HistoryItem *item); + + // Usually all new items are added to the last block. + // Only when we scroll up and add a new slice to the + // front we want to create a new front block. + void startBuildingFrontBlock(int expectedItemsCount = 1); + HistoryBlock *finishBuildingFrontBlock(); // Returns the built block or nullptr if nothing was added. + bool isBuildingFrontBlock() const { + return !_buildingFrontBlock.isNull(); + } + private: + enum class Flag { + f_has_pending_resized_items = (1 << 0), + f_pending_resize = (1 << 1), + }; + Q_DECLARE_FLAGS(Flags, Flag); + Q_DECL_CONSTEXPR friend inline QFlags operator|(Flags::enum_type f1, Flags::enum_type f2) noexcept { + return QFlags(f1) | f2; + } + Q_DECL_CONSTEXPR friend inline QFlags operator|(Flags::enum_type f1, QFlags f2) noexcept { + return f2 | f1; + } + Q_DECL_CONSTEXPR friend inline QFlags operator~(Flags::enum_type f) noexcept { + return ~QFlags(f); + } + Flags _flags; + + ChatListLinksMap _chatListLinks; + DialogRow *mainChatListLink() const { + auto it = _chatListLinks.constFind(0); + t_assert(it != _chatListLinks.cend()); + return it.value(); + } + uint64 _sortKeyInChatList; // like ((unixtime) << 32) | (incremented counter) + typedef QMap MediaOverviewIds; MediaOverviewIds overviewIds[OverviewCount]; int32 overviewCountData[OverviewCount]; // -1 - not loaded, 0 - all loaded, > 0 - count, but not all loaded - friend class HistoryBlock; - friend class ChannelHistory; + // A pointer to the block that is currently being built. + // We hold this pointer so we can destroy it while building + // and then create a new one if it is necessary. + struct BuildingBlock { + int expectedItemsCount = 0; // optimization for block->items.reserve() call + HistoryBlock *block = nullptr; + }; + UniquePointer _buildingFrontBlock; - void createInitialDateBlock(const QDateTime &date); - HistoryItem *addItemAfterPrevToBlock(HistoryItem *item, HistoryItem *prev, HistoryBlock *block); - HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex); - HistoryItem *addNewItem(HistoryBlock *to, bool newBlock, HistoryItem *adding, bool newMsg); - HistoryItem *addMessageGroupAfterPrevToBlock(const MTPDmessageGroup &group, HistoryItem *prev, HistoryBlock *block); - HistoryItem *addMessageGroupAfterPrev(HistoryItem *newItem, HistoryItem *prev); + // Creates if necessary a new block for adding item. + // Depending on isBuildingFrontBlock() gets front or back block. + HistoryBlock *prepareBlockForAddingItem(); -}; + }; class HistoryGroup; class HistoryCollapse; @@ -406,6 +597,8 @@ public: void checkJoinedMessage(bool createUnread = false); const QDateTime &maxReadMessageDate(); + ~ChannelHistory(); + private: friend class History; @@ -587,15 +780,15 @@ struct DialogsList { if (sortMode != DialogsSortByDate) return; DialogRow *change = row; - if (change != begin && begin->history->posInDialogs < row->history->posInDialogs) { + if (change != begin && begin->history->sortKeyInChatList() < row->history->sortKeyInChatList()) { change = begin; - } else while (change->prev && change->prev->history->posInDialogs < row->history->posInDialogs) { + } else while (change->prev && change->prev->history->sortKeyInChatList() < row->history->sortKeyInChatList()) { change = change->prev; } if (!insertBefore(row, change)) { - if (change->next != end && end->prev->history->posInDialogs > row->history->posInDialogs) { + if (change->next != end && end->prev->history->sortKeyInChatList() > row->history->sortKeyInChatList()) { change = end->prev; - } else while (change->next != end && change->next->history->posInDialogs > row->history->posInDialogs) { + } else while (change->next != end && change->next->history->sortKeyInChatList() > row->history->sortKeyInChatList()) { change = change->next; } insertAfter(row, change); @@ -643,22 +836,19 @@ struct DialogsIndexed { DialogsIndexed(DialogsSortMode sortMode) : sortMode(sortMode), list(sortMode) { } - History::DialogLinks addToEnd(History *history) { - History::DialogLinks result; + History::ChatListLinksMap addToEnd(History *history) { + History::ChatListLinksMap result; DialogsList::RowByPeer::const_iterator i = list.rowByPeer.find(history->peer->id); - if (i != list.rowByPeer.cend()) { - return i.value()->history->dialogs; - } - - result.insert(0, list.addToEnd(history)); - for (PeerData::NameFirstChars::const_iterator i = history->peer->chars.cbegin(), e = history->peer->chars.cend(); i != e; ++i) { - DialogsIndex::iterator j = index.find(*i); - if (j == index.cend()) { - j = index.insert(*i, new DialogsList(sortMode)); + if (i == list.rowByPeer.cend()) { + result.insert(0, list.addToEnd(history)); + for (PeerData::NameFirstChars::const_iterator i = history->peer->chars.cbegin(), e = history->peer->chars.cend(); i != e; ++i) { + DialogsIndex::iterator j = index.find(*i); + if (j == index.cend()) { + j = index.insert(*i, new DialogsList(sortMode)); + } + result.insert(*i, j.value()->addToEnd(history)); } - result.insert(*i, j.value()->addToEnd(history)); } - return result; } @@ -679,8 +869,8 @@ struct DialogsIndexed { return res; } - void adjustByPos(const History::DialogLinks &links) { - for (History::DialogLinks::const_iterator i = links.cbegin(), e = links.cend(); i != e; ++i) { + void adjustByPos(const History::ChatListLinksMap &links) { + for (History::ChatListLinksMap::const_iterator i = links.cbegin(), e = links.cend(); i != e; ++i) { if (i.key() == QChar(0)) { list.adjustByPos(i.value()); } else { @@ -719,9 +909,12 @@ struct DialogsIndexed { class HistoryBlock { public: - HistoryBlock(History *hist) : y(0), height(0), history(hist) { + HistoryBlock(History *hist) : y(0), height(0), history(hist), _indexInHistory(-1) { } + HistoryBlock(const HistoryBlock &) = delete; + HistoryBlock &operator=(const HistoryBlock &) = delete; + typedef QVector Items; Items items; @@ -731,9 +924,29 @@ public: } void removeItem(HistoryItem *item); - int32 geomResize(int32 newWidth, int32 *ytransform, const HistoryItem *resizedItem); // return new size + int resizeGetHeight(int newWidth, bool resizeAllItems); int32 y, height; History *history; + + HistoryBlock *previous() const { + t_assert(_indexInHistory >= 0); + + return (_indexInHistory > 0) ? history->blocks.at(_indexInHistory - 1) : nullptr; + } + void setIndexInHistory(int index) { + _indexInHistory = index; + } + int indexInHistory() const { + t_assert(_indexInHistory >= 0); + t_assert(history->blocks.at(_indexInHistory) == this); + + return _indexInHistory; + } + +protected: + + int _indexInHistory; + }; class HistoryElem { @@ -762,47 +975,362 @@ protected: }; -class HistoryReply; // dynamic_cast optimize class HistoryMessage; // dynamic_cast optimize -class HistoryForwarded; // dynamic_cast optimize enum HistoryCursorState { HistoryDefaultCursorState, HistoryInTextCursorState, - HistoryInDateCursorState + HistoryInDateCursorState, + HistoryInForwardedCursorState, }; enum InfoDisplayType { InfoDisplayDefault, InfoDisplayOverImage, + InfoDisplayOverBackground, }; -inline bool isImportantChannelMessage(MsgId id, int32 flags) { // client-side important msgs always has_views or has_from_id - return (flags & MTPDmessage::flag_out) || (flags & MTPDmessage::flag_mentioned) || ((id > 0 || flags != 0) && !(flags & MTPDmessage::flag_from_id)); +inline bool isImportantChannelMessage(MsgId id, MTPDmessage::Flags flags) { // client-side important msgs always has_views or has_from_id + return (flags & MTPDmessage::Flag::f_out) || (flags & MTPDmessage::Flag::f_mentioned) || (flags & MTPDmessage::Flag::f_post); } enum HistoryItemType { HistoryItemMsg = 0, - HistoryItemDate, - HistoryItemUnreadBar, HistoryItemGroup, HistoryItemCollapse, HistoryItemJoined }; +struct HistoryMessageVia : public BaseComponent { + void create(int32 userId); + void resize(int32 availw) const; + + UserData *_bot = nullptr; + mutable QString _text; + mutable int _width = 0; + mutable int _maxWidth = 0; + ClickHandlerPtr _lnk; +}; + +struct HistoryMessageViews : public BaseComponent { + QString _viewsText; + int _views = 0; + int _viewsWidth = 0; +}; + +struct HistoryMessageSigned : public BaseComponent { + void create(UserData *from, const QDateTime &date); + int maxWidth() const; + + Text _signature; +}; + +struct HistoryMessageForwarded : public BaseComponent { + void create(const HistoryMessageVia *via) const; + + PeerData *_authorOriginal = nullptr; + PeerData *_fromOriginal = nullptr; + MsgId _originalId = 0; + mutable Text _text = { 1 }; +}; + +struct HistoryMessageReply : public BaseComponent { + HistoryMessageReply &operator=(HistoryMessageReply &&other) { + replyToMsgId = other.replyToMsgId; + std::swap(replyToMsg, other.replyToMsg); + replyToLnk = std_::move(other.replyToLnk); + replyToName = std_::move(other.replyToName); + replyToText = std_::move(other.replyToText); + replyToVersion = other.replyToVersion; + _maxReplyWidth = other._maxReplyWidth; + _replyToVia = std_::move(other._replyToVia); + return *this; + } + ~HistoryMessageReply() { + // clearData() should be called by holder + t_assert(replyToMsg == nullptr); + t_assert(_replyToVia.data() == nullptr); + } + + bool updateData(HistoryMessage *holder, bool force = false); + void clearData(HistoryMessage *holder); // must be called before destructor + + void checkNameUpdate() const; + void updateName() const; + void resize(int width) const; + void itemRemoved(HistoryMessage *holder, HistoryItem *removed); + + enum PaintFlag { + PaintInBubble = 0x01, + PaintSelected = 0x02, + }; + Q_DECLARE_FLAGS(PaintFlags, PaintFlag); + void paint(Painter &p, const HistoryItem *holder, int x, int y, int w, PaintFlags flags) const; + + MsgId replyToId() const { + return replyToMsgId; + } + int replyToWidth() const { + return _maxReplyWidth; + } + ClickHandlerPtr replyToLink() const { + return replyToLnk; + } + + MsgId replyToMsgId = 0; + HistoryItem *replyToMsg = nullptr; + ClickHandlerPtr replyToLnk; + mutable Text replyToName, replyToText; + mutable int replyToVersion = 0; + mutable int _maxReplyWidth = 0; + UniquePointer _replyToVia; + int toWidth = 0; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(HistoryMessageReply::PaintFlags); + +class ReplyKeyboard; +struct HistoryMessageReplyMarkup : public BaseComponent { + HistoryMessageReplyMarkup() = default; + HistoryMessageReplyMarkup(MTPDreplyKeyboardMarkup::Flags f) : flags(f) { + } + + void create(const MTPReplyMarkup &markup); + + struct Button { + enum Type { + Default, + Url, + Callback, + RequestPhone, + RequestLocation, + SwitchInline, + }; + Type type; + QString text; + QByteArray data; + mutable mtpRequestId requestId; + }; + using ButtonRow = QVector