diff --git a/.gitignore b/.gitignore index 94b1d353e..49c8fc4d5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,14 @@ /Telegram/SourceFiles/art/grid_200x.png /Telegram/SourceFiles/art/sprite_125x.png /Telegram/SourceFiles/art/sprite_150x.png +/Telegram/Resources/art/grid.png +/Telegram/Resources/art/grid_125x.png +/Telegram/Resources/art/grid_150x.png +/Telegram/Resources/art/grid_200x.png +/Telegram/Resources/art/sprite_125x.png +/Telegram/Resources/art/sprite_150x.png /Telegram/*.user +*.vcxproj.user *.suo *.sdf *.opensdf diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..11f305e48 --- /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 000000000..9c917be1d --- /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 000000000..7deae1f95 --- /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 000000000..3fe7c7fd3 --- /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 000000000..dd3bb1967 --- /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 8d7c28ddf..32398eb24 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ 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 diff --git a/Telegram.sln b/Telegram.sln index 836d4a043..6487c468a 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 788f9c766..fcfe23316 100644 --- a/Telegram/Build.bat +++ b/Telegram/Build.bat @@ -56,7 +56,7 @@ if %BetaVersion% neq 0 ( exit /b 1 ) ) -cd SourceFiles\ +cd Resources\ if "%1" == "fast" ( echo Skipping touching of telegram.qrc.. ) else ( diff --git a/Telegram/Build.sh b/Telegram/Build.sh index 650e3692b..cf02941e0 100755 --- a/Telegram/Build.sh +++ b/Telegram/Build.sh @@ -192,7 +192,7 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "mac32" ] || [ "$BuildTarg DropboxSymbolsPath="./../../../Dropbox/Telegram/symbols" if [ "$FastParam" != "fast" ]; then - touch "./SourceFiles/telegram.qrc" + touch "./Resources/telegram.qrc" fi xcodebuild -project Telegram.xcodeproj -alltargets -configuration Release build diff --git a/Telegram/MetaLang.pro b/Telegram/MetaLang.pro index dbef70ae9..0dfa29a6f 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 2ef67ea2e..a68a44385 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 000000000..658122760 --- /dev/null +++ b/Telegram/Resources/LangList @@ -0,0 +1 @@ +de,es,it,ko,nl,pt_BR diff --git a/Telegram/SourceFiles/art/bg.jpg b/Telegram/Resources/art/bg.jpg similarity index 100% rename from Telegram/SourceFiles/art/bg.jpg rename to Telegram/Resources/art/bg.jpg diff --git a/Telegram/SourceFiles/art/bg0.png b/Telegram/Resources/art/bg0.png similarity index 100% rename from Telegram/SourceFiles/art/bg0.png rename to Telegram/Resources/art/bg0.png diff --git a/Telegram/SourceFiles/art/blank.gif b/Telegram/Resources/art/blank.gif similarity index 100% rename from Telegram/SourceFiles/art/blank.gif rename to Telegram/Resources/art/blank.gif diff --git a/Telegram/SourceFiles/art/channelcolor1.png b/Telegram/Resources/art/channelcolor1.png similarity index 100% rename from Telegram/SourceFiles/art/channelcolor1.png rename to Telegram/Resources/art/channelcolor1.png diff --git a/Telegram/SourceFiles/art/channelcolor2.png b/Telegram/Resources/art/channelcolor2.png similarity index 100% rename from Telegram/SourceFiles/art/channelcolor2.png rename to Telegram/Resources/art/channelcolor2.png diff --git a/Telegram/SourceFiles/art/channelcolor3.png b/Telegram/Resources/art/channelcolor3.png similarity index 100% rename from Telegram/SourceFiles/art/channelcolor3.png rename to Telegram/Resources/art/channelcolor3.png diff --git a/Telegram/SourceFiles/art/channelcolor4.png b/Telegram/Resources/art/channelcolor4.png similarity index 100% rename from Telegram/SourceFiles/art/channelcolor4.png rename to Telegram/Resources/art/channelcolor4.png diff --git a/Telegram/SourceFiles/art/chatcolor1.png b/Telegram/Resources/art/chatcolor1.png similarity index 100% rename from Telegram/SourceFiles/art/chatcolor1.png rename to Telegram/Resources/art/chatcolor1.png diff --git a/Telegram/SourceFiles/art/chatcolor2.png b/Telegram/Resources/art/chatcolor2.png similarity index 100% rename from Telegram/SourceFiles/art/chatcolor2.png rename to Telegram/Resources/art/chatcolor2.png diff --git a/Telegram/SourceFiles/art/chatcolor3.png b/Telegram/Resources/art/chatcolor3.png similarity index 100% rename from Telegram/SourceFiles/art/chatcolor3.png rename to Telegram/Resources/art/chatcolor3.png diff --git a/Telegram/SourceFiles/art/chatcolor4.png b/Telegram/Resources/art/chatcolor4.png similarity index 100% rename from Telegram/SourceFiles/art/chatcolor4.png rename to Telegram/Resources/art/chatcolor4.png diff --git a/Telegram/SourceFiles/art/emoji.webp b/Telegram/Resources/art/emoji.webp similarity index 100% rename from Telegram/SourceFiles/art/emoji.webp rename to Telegram/Resources/art/emoji.webp diff --git a/Telegram/SourceFiles/art/emoji_125x.webp b/Telegram/Resources/art/emoji_125x.webp similarity index 100% rename from Telegram/SourceFiles/art/emoji_125x.webp rename to Telegram/Resources/art/emoji_125x.webp diff --git a/Telegram/SourceFiles/art/emoji_150x.webp b/Telegram/Resources/art/emoji_150x.webp similarity index 100% rename from Telegram/SourceFiles/art/emoji_150x.webp rename to Telegram/Resources/art/emoji_150x.webp diff --git a/Telegram/SourceFiles/art/emoji_200x.webp b/Telegram/Resources/art/emoji_200x.webp similarity index 100% rename from Telegram/SourceFiles/art/emoji_200x.webp rename to Telegram/Resources/art/emoji_200x.webp diff --git a/Telegram/SourceFiles/art/emoji_250x.webp b/Telegram/Resources/art/emoji_250x.webp similarity index 100% rename from Telegram/SourceFiles/art/emoji_250x.webp rename to Telegram/Resources/art/emoji_250x.webp diff --git a/Telegram/SourceFiles/art/favicon.ico b/Telegram/Resources/art/favicon.ico similarity index 100% rename from Telegram/SourceFiles/art/favicon.ico rename to Telegram/Resources/art/favicon.ico diff --git a/Telegram/SourceFiles/art/fonts/OpenSans-Bold.ttf b/Telegram/Resources/art/fonts/OpenSans-Bold.ttf similarity index 100% rename from Telegram/SourceFiles/art/fonts/OpenSans-Bold.ttf rename to Telegram/Resources/art/fonts/OpenSans-Bold.ttf diff --git a/Telegram/SourceFiles/art/fonts/OpenSans-Regular.ttf b/Telegram/Resources/art/fonts/OpenSans-Regular.ttf similarity index 100% rename from Telegram/SourceFiles/art/fonts/OpenSans-Regular.ttf rename to Telegram/Resources/art/fonts/OpenSans-Regular.ttf diff --git a/Telegram/SourceFiles/art/fonts/OpenSans-Semibold.ttf b/Telegram/Resources/art/fonts/OpenSans-Semibold.ttf similarity index 100% rename from Telegram/SourceFiles/art/fonts/OpenSans-Semibold.ttf rename to Telegram/Resources/art/fonts/OpenSans-Semibold.ttf diff --git a/Telegram/SourceFiles/art/icon128.png b/Telegram/Resources/art/icon128.png similarity index 100% rename from Telegram/SourceFiles/art/icon128.png rename to Telegram/Resources/art/icon128.png diff --git a/Telegram/SourceFiles/art/icon128@2x.png b/Telegram/Resources/art/icon128@2x.png similarity index 100% rename from Telegram/SourceFiles/art/icon128@2x.png rename to Telegram/Resources/art/icon128@2x.png diff --git a/Telegram/SourceFiles/art/icon16.png b/Telegram/Resources/art/icon16.png similarity index 100% rename from Telegram/SourceFiles/art/icon16.png rename to Telegram/Resources/art/icon16.png diff --git a/Telegram/SourceFiles/art/icon16@2x.png b/Telegram/Resources/art/icon16@2x.png similarity index 100% rename from Telegram/SourceFiles/art/icon16@2x.png rename to Telegram/Resources/art/icon16@2x.png diff --git a/Telegram/SourceFiles/art/icon256.ico b/Telegram/Resources/art/icon256.ico similarity index 100% rename from Telegram/SourceFiles/art/icon256.ico rename to Telegram/Resources/art/icon256.ico diff --git a/Telegram/SourceFiles/art/icon256.png b/Telegram/Resources/art/icon256.png similarity index 100% rename from Telegram/SourceFiles/art/icon256.png rename to Telegram/Resources/art/icon256.png diff --git a/Telegram/SourceFiles/art/icon256@2x.png b/Telegram/Resources/art/icon256@2x.png similarity index 100% rename from Telegram/SourceFiles/art/icon256@2x.png rename to Telegram/Resources/art/icon256@2x.png diff --git a/Telegram/SourceFiles/art/icon32.png b/Telegram/Resources/art/icon32.png similarity index 100% rename from Telegram/SourceFiles/art/icon32.png rename to Telegram/Resources/art/icon32.png diff --git a/Telegram/SourceFiles/art/icon32@2x.png b/Telegram/Resources/art/icon32@2x.png similarity index 100% rename from Telegram/SourceFiles/art/icon32@2x.png rename to Telegram/Resources/art/icon32@2x.png diff --git a/Telegram/SourceFiles/art/icon48.png b/Telegram/Resources/art/icon48.png similarity index 100% rename from Telegram/SourceFiles/art/icon48.png rename to Telegram/Resources/art/icon48.png diff --git a/Telegram/SourceFiles/art/icon48@2x.png b/Telegram/Resources/art/icon48@2x.png similarity index 100% rename from Telegram/SourceFiles/art/icon48@2x.png rename to Telegram/Resources/art/icon48@2x.png diff --git a/Telegram/SourceFiles/art/icon512.png b/Telegram/Resources/art/icon512.png similarity index 100% rename from Telegram/SourceFiles/art/icon512.png rename to Telegram/Resources/art/icon512.png diff --git a/Telegram/SourceFiles/art/icon512@2x.png b/Telegram/Resources/art/icon512@2x.png similarity index 100% rename from Telegram/SourceFiles/art/icon512@2x.png rename to Telegram/Resources/art/icon512@2x.png diff --git a/Telegram/SourceFiles/art/icon64.png b/Telegram/Resources/art/icon64.png similarity index 100% rename from Telegram/SourceFiles/art/icon64.png rename to Telegram/Resources/art/icon64.png diff --git a/Telegram/SourceFiles/art/icon64@2x.png b/Telegram/Resources/art/icon64@2x.png similarity index 100% rename from Telegram/SourceFiles/art/icon64@2x.png rename to Telegram/Resources/art/icon64@2x.png diff --git a/Telegram/SourceFiles/art/icon_green.png b/Telegram/Resources/art/icon_green.png similarity index 100% rename from Telegram/SourceFiles/art/icon_green.png rename to Telegram/Resources/art/icon_green.png diff --git a/Telegram/SourceFiles/art/iconbig256.png b/Telegram/Resources/art/iconbig256.png similarity index 100% rename from Telegram/SourceFiles/art/iconbig256.png rename to Telegram/Resources/art/iconbig256.png diff --git a/Telegram/SourceFiles/art/iconbig_green.png b/Telegram/Resources/art/iconbig_green.png similarity index 100% rename from Telegram/SourceFiles/art/iconbig_green.png rename to Telegram/Resources/art/iconbig_green.png diff --git a/Telegram/SourceFiles/art/newmsg.wav b/Telegram/Resources/art/newmsg.wav similarity index 100% rename from Telegram/SourceFiles/art/newmsg.wav rename to Telegram/Resources/art/newmsg.wav diff --git a/Telegram/SourceFiles/art/osxsetup.tif b/Telegram/Resources/art/osxsetup.tif similarity index 100% rename from Telegram/SourceFiles/art/osxsetup.tif rename to Telegram/Resources/art/osxsetup.tif diff --git a/Telegram/SourceFiles/art/osxsetup.tiff b/Telegram/Resources/art/osxsetup.tiff similarity index 100% rename from Telegram/SourceFiles/art/osxsetup.tiff rename to Telegram/Resources/art/osxsetup.tiff diff --git a/Telegram/SourceFiles/art/osxsetup@2x.tif b/Telegram/Resources/art/osxsetup@2x.tif similarity index 100% rename from Telegram/SourceFiles/art/osxsetup@2x.tif rename to Telegram/Resources/art/osxsetup@2x.tif diff --git a/Telegram/SourceFiles/art/osxtray.png b/Telegram/Resources/art/osxtray.png similarity index 100% rename from Telegram/SourceFiles/art/osxtray.png rename to Telegram/Resources/art/osxtray.png diff --git a/Telegram/SourceFiles/art/sprite.png b/Telegram/Resources/art/sprite.png similarity index 90% rename from Telegram/SourceFiles/art/sprite.png rename to Telegram/Resources/art/sprite.png index 86e0fd4ec..b9c755857 100644 Binary files a/Telegram/SourceFiles/art/sprite.png and b/Telegram/Resources/art/sprite.png differ diff --git a/Telegram/SourceFiles/art/sprite_200x.png b/Telegram/Resources/art/sprite_200x.png similarity index 84% rename from Telegram/SourceFiles/art/sprite_200x.png rename to Telegram/Resources/art/sprite_200x.png index 3c2c54606..964c993cf 100644 Binary files a/Telegram/SourceFiles/art/sprite_200x.png and b/Telegram/Resources/art/sprite_200x.png differ diff --git a/Telegram/SourceFiles/art/usercolor1.png b/Telegram/Resources/art/usercolor1.png similarity index 100% rename from Telegram/SourceFiles/art/usercolor1.png rename to Telegram/Resources/art/usercolor1.png diff --git a/Telegram/SourceFiles/art/usercolor2.png b/Telegram/Resources/art/usercolor2.png similarity index 100% rename from Telegram/SourceFiles/art/usercolor2.png rename to Telegram/Resources/art/usercolor2.png diff --git a/Telegram/SourceFiles/art/usercolor3.png b/Telegram/Resources/art/usercolor3.png similarity index 100% rename from Telegram/SourceFiles/art/usercolor3.png rename to Telegram/Resources/art/usercolor3.png diff --git a/Telegram/SourceFiles/art/usercolor4.png b/Telegram/Resources/art/usercolor4.png similarity index 100% rename from Telegram/SourceFiles/art/usercolor4.png rename to Telegram/Resources/art/usercolor4.png diff --git a/Telegram/SourceFiles/art/usercolor5.png b/Telegram/Resources/art/usercolor5.png similarity index 100% rename from Telegram/SourceFiles/art/usercolor5.png rename to Telegram/Resources/art/usercolor5.png diff --git a/Telegram/SourceFiles/art/usercolor6.png b/Telegram/Resources/art/usercolor6.png similarity index 100% rename from Telegram/SourceFiles/art/usercolor6.png rename to Telegram/Resources/art/usercolor6.png diff --git a/Telegram/SourceFiles/art/usercolor7.png b/Telegram/Resources/art/usercolor7.png similarity index 100% rename from Telegram/SourceFiles/art/usercolor7.png rename to Telegram/Resources/art/usercolor7.png diff --git a/Telegram/SourceFiles/art/usercolor8.png b/Telegram/Resources/art/usercolor8.png similarity index 100% rename from Telegram/SourceFiles/art/usercolor8.png rename to Telegram/Resources/art/usercolor8.png diff --git a/Telegram/SourceFiles/etc/qt_linux.conf b/Telegram/Resources/etc/qt_linux.conf similarity index 100% rename from Telegram/SourceFiles/etc/qt_linux.conf rename to Telegram/Resources/etc/qt_linux.conf diff --git a/Telegram/SourceFiles/etc/qt_win.conf b/Telegram/Resources/etc/qt_win.conf similarity index 100% rename from Telegram/SourceFiles/etc/qt_win.conf rename to Telegram/Resources/etc/qt_win.conf diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index c5b317fdd..ac8ec11f8 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -399,7 +399,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_preview_loading" = "Getting Link Info..."; "lng_profile_chat_unaccessible" = "Group is unaccessible"; -"lng_topbar_info" = "Info"; "lng_profile_about_section" = "About"; "lng_profile_description_section" = "Description"; "lng_profile_settings_section" = "Settings"; @@ -601,6 +600,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "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 available 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"; @@ -685,8 +689,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "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_invite_privacy" = "Sorry, you cannot add this user to groups because of their privacy settings."; +"lng_cant_invite_privacy_channel" = "Sorry, you cannot add this user to channels because of their privacy settings."; "lng_cant_do_this" = "Sorry, this action is unavailable."; "lng_send_button" = "Send"; @@ -810,6 +814,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forward_messages" = "{count:_not_used_|Forwarded message|# forwarded messages}"; "lng_forwarding_from" = "{user} and {count:_not_used_|# other|# others}"; "lng_forwarding_from_two" = "{user} and {second_user}"; +"lng_inline_switch_choose" = "Choose conversation..."; +"lng_inline_switch_cant" = "Sorry, no way to write here :("; "lng_share_cant" = "Sorry, no way to share here :("; "lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :("; @@ -886,12 +892,16 @@ Copyright (c) 2014-2016 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" = "PUBLIC GROUPS, PINNED POSTS, 5,000 MEMBERS\n\n— Groups can now have 5,000 members (up from 1,000)\n— Groups of any size may be converted to supergroups\n\nNew tools for supergroup admins:\n\n— Make your group public by setting up a public link – anyone will be able to view the chat and join it\n— Pin messages to keep important updates visible and notify all members\n— Select messages to delete, report as spam, block users, or remove all messages from a user\n\nMore about this update:\n{link}"; +"lng_new_version_text" = "BOTS 2.0\n\n— Introducing Bot API 2.0, the biggest update to our bot platform since June 2015.\n— Bots can now update existing messages on the fly as you interact with them.\n— New Inline keyboards with callback, 'open URL' or 'switch to inline mode' buttons help create seamless interfaces.\n— Inline bots can now send all attachments supported by Telegram (videos, music, stickers, locations, etc.).\n— Try out these sample bots to see what's coming your way soon: @music, @sticker, @youtube, @foursquare\n\nMore info: {link}"; "lng_menu_insert_unicode" = "Insert Unicode control character"; "lng_full_name" = "{first_name} {last_name}"; +// Not used + +"lng_topbar_info" = "Info"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/langs/lang_de.strings b/Telegram/Resources/langs/lang_de.strings similarity index 96% rename from Telegram/SourceFiles/langs/lang_de.strings rename to Telegram/Resources/langs/lang_de.strings index a89dc41e8..e24075c48 100644 --- a/Telegram/SourceFiles/langs/lang_de.strings +++ b/Telegram/Resources/langs/lang_de.strings @@ -544,7 +544,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_action_pinned_media" = "{from} hat {media} angeheftet"; "lng_action_pinned_media_photo" = "ein Bild"; "lng_action_pinned_media_video" = "ein Video"; -"lng_action_pinned_media_audio" = "ein Audio"; +"lng_action_pinned_media_audio" = "eine Audiodatei"; "lng_action_pinned_media_voice" = "eine Sprachnachricht"; "lng_action_pinned_media_file" = "eine Datei"; "lng_action_pinned_media_gif" = "ein GIF"; @@ -601,6 +601,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "Antwort auf"; +"lng_bot_share_location_unavailable" = "Teilen von Standorten ist derzeit bei Telegram Desktop nicht möglich."; +"lng_bot_inline_geo_unavailable" = "Dieser Bot braucht deinen aktuellen Standort. Die Funktion ist bei Telegram Desktop derzeit nicht verfügbar."; +"lng_bot_share_phone" = "Telefonnummer teilen?"; +"lng_bot_share_phone_confirm" = "Teilen"; + "lng_attach_failed" = "Uploadfehler"; "lng_attach_file" = "Datei"; "lng_attach_photo" = "Bild"; @@ -685,8 +690,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_cant_invite_not_contact_channel" = "Du kannst nur Personen hinzufügen,\nwenn ihr eure Nummern ausgetauscht habt.\n{more_info}"; "lng_cant_more_info" = "Weitere Infos »"; "lng_cant_invite_banned" = "Nur Admins können diesen Nutzer hinzufügen."; -"lng_cant_invite_privacy" = "Du kannst mit diesen Nutzern keine Gruppe erstellen, weil sie es nicht erlauben."; -"lng_cant_invite_privacy_channel" = "Du kannst diese Nutzer keinen Kanälen hinzufügen, weil sie es nicht erlauben."; +"lng_cant_invite_privacy" = "Du kannst mit diesem Nutzer keine Gruppe erstellen, weil er es nicht erlaubt."; +"lng_cant_invite_privacy_channel" = "Du kannst diesen Nutzer keinen Kanälen hinzufügen, weil er es nicht erlaubt."; "lng_cant_do_this" = "Verzeihung. Das ist leider nicht möglich."; "lng_send_button" = "Senden"; @@ -698,7 +703,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_will_be_notified" = "Mitglieder werden benachrichtigt"; "lng_wont_be_notified" = "Mitglieder werden nicht benachrichtigt"; "lng_empty_history" = ""; -"lng_willbe_history" = "Chat auswählen um zu schreiben"; +"lng_willbe_history" = "Chat auswählen, um zu schreiben"; "lng_message_with_from" = "[c]{from}:[/c] {message}"; "lng_from_you" = "Ich"; "lng_bot_description" = "Was kann dieser Bot?"; @@ -725,10 +730,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_user_action_record_video" = "{user} sendet ein Video"; "lng_send_action_upload_video" = "schickt ein Video"; "lng_user_action_upload_video" = "{user} sendet ein Video"; -"lng_send_action_record_audio" = "nimmt ein Audio auf"; -"lng_user_action_record_audio" = "{user} nimmt eine Sprachnachricht auf"; -"lng_send_action_upload_audio" = "sendet eine Sprachnachricht"; -"lng_user_action_upload_audio" = "{user} sendet eine Sprachnachricht"; +"lng_send_action_record_audio" = "nimmt Sprachnachricht auf"; +"lng_user_action_record_audio" = "{user} nimmt Sprachnachricht auf"; +"lng_send_action_upload_audio" = "sendet Sprachnachricht"; +"lng_user_action_upload_audio" = "{user} sendet Sprachnachricht"; "lng_send_action_upload_photo" = "sendet ein Bild"; "lng_user_action_upload_photo" = "{user} sendet ein Bild"; "lng_send_action_upload_file" = "sendet eine Datei"; @@ -810,6 +815,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forward_messages" = "{count:_not_used_|Nachrichtenanhang|# Nachrichtenanhänge}"; "lng_forwarding_from" = "{user} und {count:_not_used_|# anderer|# andere}"; "lng_forwarding_from_two" = "{user} und {second_user}"; +"lng_inline_switch_choose" = "Chat wählen..."; +"lng_inline_switch_cant" = "Hier kannst du nicht schreiben :("; "lng_share_cant" = "Weiterleitung nicht möglich :("; "lng_reply_cant" = "Leider kann man alte Nachrichten in Supergruppen nicht beantworten :("; @@ -886,7 +893,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram Desktop wurde aktualisiert auf Version {version}\n\n{changes}\n\nGesamter Versionsverlauf:\n{link}"; "lng_new_version_minor" = "— Fehlerbehebungen und Softwareoptimierungen"; -"lng_new_version_text" = "ÖFFENTLICHE GRUPPEN, NACHRICHTEN ANHEFTEN, 5000 MITGLIEDER\n\n— Gruppen dürfen nun 5.000 Mitglieder haben (zuvor waren es 1.000)\n— Jede Gruppe - egal wie groß - kann ab sofort in eine Supergruppe geändert werden\n\nNeue Werkzeuge für Supergruppen-Admins:\n\n— Mache deine Gruppe öffentlich: Jeder kann den Inhalt einsehen und sie betreten\n— Hefte Nachrichten an: Perfekt um Gruppenmitglieder über Neuigkeiten zu informieren\n— Lösche mehrere Nachrichten, melde sie aufgrund von Spam, blockiere Mitglieder oder entferne alle Nachrichten von bestimmten Nutzern\n\nMehr Infos zum neuen Update:\n{link}"; +"lng_new_version_text" = "BOTS 2.0\n\n— Bot API 2.0: Das größte Update unserer Bot-Plattform seit Juni 2015.\n— Bots können vorhandene Nachrichten aktualisieren, während du mit dem Bot schreibst.\n— Neue Inline-Tastaturen mit 'Callback', 'URL öffnen' oder 'in den Inline-Modus wechseln'-Knöpfen, für eine noch bessere Integration.\n— Inline-Bots dürfen nun alle erlaubten Anhänge senden (Videos, Musik, Sticker, Dateien, etc.).\n— Probiere doch mal einen der folgenden Beispielbots aus: @music, @sticker, @youtube, @foursquare\n\nMehr Infos: {link}"; "lng_menu_insert_unicode" = "Unicode-Steuerzeichen einfügen"; diff --git a/Telegram/SourceFiles/langs/lang_es.strings b/Telegram/Resources/langs/lang_es.strings similarity index 97% rename from Telegram/SourceFiles/langs/lang_es.strings rename to Telegram/Resources/langs/lang_es.strings index 79a8f1247..88da8efdc 100644 --- a/Telegram/SourceFiles/langs/lang_es.strings +++ b/Telegram/Resources/langs/lang_es.strings @@ -601,6 +601,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "Respondiendo a"; +"lng_bot_share_location_unavailable" = "Lo sentimos, compartir la ubicación no está disponible actualmente en Telegram Desktop."; +"lng_bot_inline_geo_unavailable" = "Lo sentimos, este bot requiere compartir la ubicación.\nNo está disponible en Telegram Desktop."; +"lng_bot_share_phone" = "¿Compartir número de teléfono?"; +"lng_bot_share_phone_confirm" = "Compartir"; + "lng_attach_failed" = "Fallido"; "lng_attach_file" = "Archivo"; "lng_attach_photo" = "Foto"; @@ -810,6 +815,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forward_messages" = "{count:_not_used_|Mensaje adjunto|# mensajes adjuntos}"; "lng_forwarding_from" = "{user} y {count:_not_used_|# otro|# otros}"; "lng_forwarding_from_two" = "{user} y {second_user}"; +"lng_inline_switch_choose" = "Elige una conversación..."; +"lng_inline_switch_cant" = "Lo sentimos, no puedes escribir aquí :("; "lng_share_cant" = "Lo sentimos. No hay forma de compartir aquí :("; "lng_reply_cant" = "Lo sentimos, no puedes responder un mensaje viejo en un supergrupo :("; @@ -886,7 +893,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram Desktop ha sido actualizada a la versión {version}\n\n{changes}\n\nEl historial completo está disponible aquí:\n{link}"; "lng_new_version_minor" = "— Corrección de errores y otras mejoras menores"; -"lng_new_version_text" = "GRUPOS PÚBLICOS, PUBLICACIONES ANCLADAS, 5000 MIEMBROS\n\n— Los grupos ahora pueden tener hasta 5000 miembros (antes eran 1000)\n— Los grupos de cualquier tamaño pueden ser convertidos en supergrupos\n\nNuevas herramientas para los administradores de supergrupos:\n\n— Haz público tu grupo, generando un enlace público. Cualquiera podrá ver el chat y unirse.\n— Ancla mensajes, para mantener visible lo importante, y notifica a todos los miembros.\n— Elige varios mensajes para eliminar, reportar como spam, bloquear usuarios o quitar todos los mensajes de un usuario en particular.\n\nMás sobre esta actualización:\n{link}"; +"lng_new_version_text" = "BOTS 2.0\n\n— Presentamos la API para bots 2.0, la actualización más grande en nuestra plataforma para bots desde junio de 2015.\n— Desde ahora, los bots pueden actualizar los mensajes existentes sobre la marcha, al interactuar con ellos.\n— Los nuevos teclados integrados con retrollamada, botones para ‘abrir URL’ o ‘cambiar al modo integrado’, ayudan a crear una interfaz fluida.\n— Ahora los bots integrados pueden enviar todos los adjuntos soportados en Telegram (vídeos, música, stickers, archivos, etc.).\n— Prueba estos ejemplos de bots para que veas lo que viene: @music, @sticker, @youtube, @foursquare\n\nMás en: {link}"; "lng_menu_insert_unicode" = "Insertar caracteres de control Unicode"; diff --git a/Telegram/SourceFiles/langs/lang_it.strings b/Telegram/Resources/langs/lang_it.strings similarity index 96% rename from Telegram/SourceFiles/langs/lang_it.strings rename to Telegram/Resources/langs/lang_it.strings index 2dd317d7e..ebdce80d8 100644 --- a/Telegram/SourceFiles/langs/lang_it.strings +++ b/Telegram/Resources/langs/lang_it.strings @@ -138,7 +138,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_pinned_notify" = "Notifica tutti i membri"; "lng_intro" = "Benvenuti nell'app desktop ufficiale di [a href=\"https://telegram.org/\"]Telegram[/a].\nÈ [b]veloce[/b] e [b]sicura[/b]."; -"lng_start_msgs" = "INIZIA A MESSAGGIARE"; +"lng_start_msgs" = "INIZIA A CHATTARE"; "lng_intro_next" = "AVANTI"; "lng_intro_finish" = "ISCRIVITI"; @@ -379,7 +379,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_reset_button" = "Chiudi"; "lng_settings_reset_done" = "Altre sessioni terminate"; "lng_settings_ask_question" = "Fai una domanda"; -"lng_settings_ask_sure" = "Per favore nota che il supporto di Telegram è fornito da volontari. Proviamo a rispondere quanto prima, ma potrebbe volerci del tempo.\n\nDai un'occhiata alle domande frequenti su Telegram: potrai trovare importanti suggerimenti riguardo alcune problematiche e risposte alla maggior parte delle domande."; +"lng_settings_ask_sure" = "Per favore nota che il supporto di Telegram è fornito da volontari. Proviamo a rispondere quanto prima, ma potrebbe volerci del tempo.\n\nDai un'occhiata alle domande frequenti di Telegram: contengono suggerimenti importanti per risolvere i problemi e risposte a quasi tutte le domande."; "lng_settings_faq_button" = "Domande frequenti"; "lng_settings_ask_ok" = "Chiedi"; "lng_settings_faq" = "Domande frequenti"; @@ -601,6 +601,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "In risposta a"; +"lng_bot_share_location_unavailable" = "Spiacenti, la condivisione della posizione non è al momento disponibile su Telegram Desktop."; +"lng_bot_inline_geo_unavailable" = "Spiacenti, questo bot richiede la condivisione della posizione. Non è disponibile su Telegram Desktop."; +"lng_bot_share_phone" = "Condividere il numero di telefono?"; +"lng_bot_share_phone_confirm" = "Condividi"; + "lng_attach_failed" = "Fallito"; "lng_attach_file" = "File"; "lng_attach_photo" = "Foto"; @@ -685,8 +690,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_cant_invite_not_contact_channel" = "Spiacenti, ma al momento puoi aggiungere\nai canali solo contatti reciproci.\n{more_info}"; "lng_cant_more_info" = "Più info »"; "lng_cant_invite_banned" = "Spiacenti, solo l'amministratore può aggiungere questo utente."; -"lng_cant_invite_privacy" = "Spiacenti, non puoi aggiungere questo utente al gruppo a causa delle sue impostazioni di privacy."; -"lng_cant_invite_privacy_channel" = "Spiacenti, non puoi aggiungere questo utente al canale a causa delle sue impostazioni di privacy."; +"lng_cant_invite_privacy" = "Spiacenti, non puoi aggiungere questo utente ai gruppi a causa delle sue impostazioni di privacy."; +"lng_cant_invite_privacy_channel" = "Spiacenti, non puoi aggiungere questo utente ai canali a causa delle sue impostazioni di privacy."; "lng_cant_do_this" = "Spiacenti, questa azione non è disponibile."; "lng_send_button" = "Invia"; @@ -698,7 +703,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_will_be_notified" = "I post saranno notificati ai membri"; "lng_wont_be_notified" = "I post non saranno notificati ai membri"; "lng_empty_history" = ""; -"lng_willbe_history" = "Seleziona una chat per iniziare a messaggiare"; +"lng_willbe_history" = "Seleziona una chat per iniziare a chattare"; "lng_message_with_from" = "[c]{from}:[/c] {message}"; "lng_from_you" = "Tu"; "lng_bot_description" = "Cosa può fare questo bot?"; @@ -810,6 +815,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forward_messages" = "{count:_not_used_|messaggio inoltrato|# messaggi inoltrati}"; "lng_forwarding_from" = "{user} e {count:_not_used_|# altro|altri #}"; "lng_forwarding_from_two" = "{user} e {second_user}"; +"lng_inline_switch_choose" = "Scegli conversazione..."; +"lng_inline_switch_cant" = "Spiacenti, impossibile scrivere qui :("; "lng_share_cant" = "Spiacenti, impossibile condividere qui :("; "lng_reply_cant" = "Spiacenti, non si può rispondere a un vecchio messaggio nel supergruppo :("; @@ -886,7 +893,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram Desktop si è aggiornato alla versione {version}\n\n{changes}\n\nLa cronologia degli aggiornamenti è disponibile qui:\n{link}"; "lng_new_version_minor" = "— Risoluzione di problemi e altri miglioramenti minori"; -"lng_new_version_text" = "GRUPPI PUBBLICI, POST FISSATI, 5000 MEMBRI\n\n— I gruppi possono ora avere fino a 5000 membri (dai precedenti 1000)\n— Puoi convertire qualsiasi gruppo in supergruppo\n\nNuovi strumenti per gli amministratori dei supergruppi:\n\n— Rendi pubblico il tuo gruppo inserendo un link - chiunque sarà in grado di vedere la chat e unirsi\n— Fissa i messaggi per rendere gli aggiornamenti importanti visibili\n— Seleziona diversi messaggi per eliminarli, segnalarli, bloccare utenti ed eliminare i loro messaggi\n\nPiù info su questo aggiornamento:\n{link}"; +"lng_new_version_text" = "BOT 2.0,\n\n— Introdotta l'API dei Bot 2.0, il più grande aggiornamento della nostra piattaforma da Giugno 2015.\n— I bot possono ora aggiornare i messaggi esistenti quando interagisci con loro.\n— Nuove tastiere per i bot inline con pulsanti callback (per non far inviare nuovi messaggi al bot), 'apri URL' o 'passa a modalità inline' per creare un'interfaccia senza interruzioni.\n— I bot inline possono ora inviare qualsiasi tipo di allegato supportato su Telegram (video,musica,sticker,file,etc.).\n— Prova questi bot di esempio per vedere cosa è in arrivo a breve: @music, @sticker, @youtube, @foursquare\n\nPiù informazioni: {link}"; "lng_menu_insert_unicode" = "Inserisci carattere di controllo Unicode"; diff --git a/Telegram/SourceFiles/langs/lang_ko.strings b/Telegram/Resources/langs/lang_ko.strings similarity index 90% rename from Telegram/SourceFiles/langs/lang_ko.strings rename to Telegram/Resources/langs/lang_ko.strings index ff30db37d..3385080a4 100644 --- a/Telegram/SourceFiles/langs/lang_ko.strings +++ b/Telegram/Resources/langs/lang_ko.strings @@ -86,8 +86,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_cancel" = "취소"; "lng_continue" = "계속"; "lng_close" = "닫기"; -"lng_connecting" = "Connecting..."; -"lng_reconnecting" = "Reconnect {count:now|in # s|in # s}..."; +"lng_connecting" = "연결중..."; +"lng_reconnecting" = "재연결중..{count:now|in # 초|in # 초}..."; "lng_reconnecting_try_now" = "다시 시도"; "lng_status_service_notifications" = "서비스 알림"; @@ -108,7 +108,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_status_lastseen_date" = "{date}에 마지막으로 접속"; "lng_status_lastseen_date_time" = "{date}일 {time}에 마지막으로 접속"; "lng_status_online" = "온라인"; -"lng_status_connecting" = "connecting..."; +"lng_status_connecting" = "연결중..."; "lng_chat_status_unaccessible" = "그룹 접근 불가"; "lng_chat_status_members" = "{count:맴버 없음|#명|#명}"; @@ -127,7 +127,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_edit_deleted" = "메시지는 삭제 되었습니다."; "lng_edit_too_long" = "메시지 길이가 너무 깁니다."; "lng_edit_message" = "메시지 수정"; -"lng_edit_message_text" = "New message text..."; +"lng_edit_message_text" = "새로운 메시지..."; "lng_deleted" = "알 수 없음"; "lng_deleted_message" = "삭제된 메시지"; "lng_pinned_message" = "고정된 메시지"; @@ -162,7 +162,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_code_telegram" = "[b]텔레그램[/b] 앱으로 부터 방금 수신받은,\n코드를 입력해주세요."; "lng_code_no_telegram" = "코드를 SMS로 전송"; "lng_code_call" = "텔레그램이 {minutes}:{seconds}후에는 전화를 겁니다."; -"lng_code_calling" = "Requesting a call from Telegram..."; +"lng_code_calling" = "텔레그램으로부터 전화 요청을 하고 있습니다..."; "lng_code_called" = "텔레그램이 회원님의 전화번호로 전화를 걸었습니다."; "lng_bad_phone" = "잘못된 전화번호입니다. 다시 시도해주세요."; @@ -201,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dlg_new_channel_name" = "채널명"; "lng_no_contacts" = "연락처가 없습니다."; "lng_no_chats" = "대화시 대화방이 존재 할 곳입니다."; -"lng_contacts_loading" = "Loading..."; +"lng_contacts_loading" = "로딩중..."; "lng_contacts_not_found" = "연락처를 찾을 수 없음"; "lng_dlg_search_chat" = "이 채팅에서 검색"; "lng_dlg_search_channel" = "이 채널방에서 검색"; @@ -210,7 +210,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_save" = "저장"; "lng_settings_upload" = "프로필 이미지 선택"; "lng_settings_crop_profile" = "프로필 사진으로 사용할 사각영역을 선택하세요"; -"lng_settings_uploading_photo" = "Uploading photo..."; +"lng_settings_uploading_photo" = "사진 업로드 중..."; "lng_username_title" = "아이디"; "lng_username_about" = "텔레그램 아이디를 설정할 수 있습니다. \n아이디를 설정하면 회원님의 전화번호를 몰라도 아이디로 회원님을 찾아 대화를 나눌 수 있습니다.\n아이디는 영문, 밑줄, 숫자로 a-z, _, 0-9, \n다섯 글자 이상으로 설정해 주세요."; @@ -247,9 +247,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_auto_update" = "자동 업데이트"; "lng_settings_current_version" = " {version}"; "lng_settings_check_now" = "업데이트 확인"; -"lng_settings_update_checking" = "Checking for updates..."; +"lng_settings_update_checking" = "업데이트 확인 중..."; "lng_settings_latest_installed" = "최신 버전이 설치되어 있습니다."; -"lng_settings_downloading" = "Downloading update {ready} / {total} MB..."; +"lng_settings_downloading" = "데이트를 다운로드 중 {ready} / {total} MB.."; "lng_settings_update_ready" = "새로운 버전을 설치 할 수 있습니다."; "lng_settings_update_now" = "재시작 합니다."; "lng_settings_update_fail" = "업데이트 확인 실패 :("; @@ -291,7 +291,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_download_path_failed" = "파일 다운로드를 시작 할 수 없습니다. 올바르지 않은 다운로드 경로가 원인 일 수도 있습니다.\n\n설정에 가시면 다운로드 경로를 변경하실 수 있습니다."; "lng_download_path_settings" = "설정"; "lng_download_finish_failed" = "파일 다운로드를 끝낼 수 없습니다.\n\n다시 시도하시겠습니까?"; -"lng_download_path_clearing" = "Clearing..."; +"lng_download_path_clearing" = "초기화 중.."; "lng_download_path_cleared" = "초기화 완료!"; "lng_download_path_clear_failed" = "초기화 실패 :("; @@ -300,7 +300,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_images_cached" = "{count:_not_used_|이미지 #개|이미지 #개}, {size}"; "lng_settings_audios_cached" = "{count:_not_used_|음성 메시지 #개|음성 메시지 #개}, {size}"; "lng_local_storage_clear" = "전체 정리"; -"lng_local_storage_clearing" = "Clearing..."; +"lng_local_storage_clearing" = "초기화 중.."; "lng_local_storage_cleared" = "초기화 완료!"; "lng_local_storage_clear_failed" = "초기화 실패 :("; @@ -331,7 +331,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_passcode_logout" = "로그아웃"; "lng_passcode_need_unblock" = "잠금코드를 먼저 해제해주세요."; -"lng_cloud_password_waiting" = "Confirmation link sent to {email}..."; +"lng_cloud_password_waiting" = "{email}로 확인 이메일을 전송하였습니다.."; "lng_cloud_password_change" = "클라우드 비밀번호 변경"; "lng_cloud_password_create" = "클라우드 비밀번호"; "lng_cloud_password_remove" = "클라우드 비밀번호 삭제"; @@ -358,9 +358,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_cloud_password_is_same" = "비밀번호가 변경되지 않았습니다."; "lng_connection_type" = "연결 유형:"; -"lng_connection_auto_connecting" = "Default (connecting...)"; +"lng_connection_auto_connecting" = "기본값 (연결중...)"; "lng_connection_auto" = "기본값 ({transport} 사용)"; -"lng_connection_proxy_connecting" = "Connecting through proxy..."; +"lng_connection_proxy_connecting" = "프록시 연결 중..."; "lng_connection_proxy" = "{transport} 프록시 연결"; "lng_connection_header" = "연결 유형"; "lng_connection_auto_rb" = "자동 (사용 가능하다면 TCP 아니면 HTTP 사용)"; @@ -396,7 +396,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_sessions_other_desc" = "동일한 휴대번호로 다른 휴대기기, 태블릿과 데스크탑에서 텔레그램 로그인이 가능합니다. 모든 데이터는 즉시 동기화 됩니다."; "lng_sessions_terminate_all" = "다른 모든 세션 강제 종료"; -"lng_preview_loading" = "Getting Link Info..."; +"lng_preview_loading" = "링크 정보를 가져오는 중.."; "lng_profile_chat_unaccessible" = "그룹에 접근할 수 없습니다."; "lng_topbar_info" = "정보"; @@ -439,7 +439,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_sure_kick" = "{user}를 추방하시겠습니까?"; "lng_profile_sure_kick_channel" = "{user}를 추방하시겠습니까?"; "lng_profile_sure_kick_admin" = "{user}를 관리자에서 제외 하시겠습니까?"; -"lng_profile_loading" = "Loading..."; +"lng_profile_loading" = "로드중.."; "lng_profile_shared_media" = "공유된 미디어"; "lng_profile_no_media" = "대화에 미디어가 존재하지 않습니다."; "lng_profile_photos" = "{count:_not_used_|#개의 사진|#개의 사진} »"; @@ -455,7 +455,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_shared_links" = "{count:_not_used_|# 공유된 링크|# 공유된 링크} »"; "lng_profile_shared_links_header" = "공유된 링크 현황"; "lng_profile_copy_phone" = "전화번호 복사"; -"lng_profile_copy_fullname" = "Copy name"; +"lng_profile_copy_fullname" = "이름 복사"; "lng_channel_add_admins" = "새로운 관리자"; "lng_channel_add_members" = "구성원 추가"; @@ -601,6 +601,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "다음 유저에게 답장 :"; +"lng_bot_share_location_unavailable" = "죄송합니다, 위치 공유는 텔레그램 테스크탑에서는 현재 지원을 하고 있지 않습니다."; +"lng_bot_inline_geo_unavailable" = "죄송합니다, 위치 공유는 텔레그램 테스크탑에서는 현재 지원을 하고 있지 않습니다."; +"lng_bot_share_phone" = "전화번호를 공유하겠습니까?"; +"lng_bot_share_phone_confirm" = "공유"; + "lng_attach_failed" = "실패"; "lng_attach_file" = "파일"; "lng_attach_photo" = "사진"; @@ -660,13 +665,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_remove" = "삭제"; "lng_stickers_return" = "실행취소"; "lng_stickers_restore" = "복구"; -"lng_stickers_count" = "{count:Loading...|# sticker|# stickers}"; +"lng_stickers_count" = "{count:Loading...|# 스티커|# 스티커}"; "lng_in_dlg_photo" = "사진"; -"lng_in_dlg_video" = "Video file"; -"lng_in_dlg_audio_file" = "Audio file"; +"lng_in_dlg_video" = "비디오 파일"; +"lng_in_dlg_audio_file" = "음성 파일"; "lng_in_dlg_contact" = "연락처"; -"lng_in_dlg_audio" = "Voice message"; +"lng_in_dlg_audio" = "음성 메시지"; "lng_in_dlg_file" = "파일"; "lng_in_dlg_sticker" = "스티커"; "lng_in_dlg_sticker_emoji" = "{emoji} (스티커)"; @@ -680,20 +685,20 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_spam_sure_group" = "선택한 그룹메시지를 스팸으로 신고하시겠습니까?"; "lng_report_spam_sure_channel" = "선택한 채널메시지를 스팸으로 신고하시겠습니까?"; "lng_report_spam_ok" = "신고하기"; -"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_send_to_not_contact" = "죄송하지만, 현재 서로 연락처가 추가된 \n회원들끼리만 전송이 가능합니다. \n{more_info}"; +"lng_cant_invite_not_contact" = "죄송하지만, 현재 그룹방에 서로 연락처가 추가된 \n회원들끼리만 추가 가능합니다. {more_info}"; +"lng_cant_invite_not_contact_channel" = "죄송하지만, 현재 채널방에 서로 연락처가 추가된 \n회원들끼리만 추가 가능합니다. \n{more_info}"; "lng_cant_more_info" = "자세한 정보 »"; -"lng_cant_invite_banned" = "Sorry, only admin can add this user."; +"lng_cant_invite_banned" = "죄송하지만, 관리자만 회원 추가가 가능합니다."; "lng_cant_invite_privacy" = "죄송합니다, 개인설정으로 인하여 이 사용자를 그룹에 초대할 수 없습니다."; "lng_cant_invite_privacy_channel" = "죄송합니다, 개인설정으로 인하여 이 사용자를 채널에 초대할 수 없습니다."; -"lng_cant_do_this" = "Sorry, this action is unavailable."; +"lng_cant_do_this" = "죄송하지만, 할 수 없는 기능입니다."; "lng_send_button" = "보내기"; -"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_message_ph" = "메시지 쓰기.."; +"lng_comment_ph" = "코멘트 쓰기..."; +"lng_broadcast_ph" = "단체메시지 쓰기..."; +"lng_broadcast_silent_ph" = "음소거 메시지..."; "lng_record_cancel" = "이 영역 밖에서 마우스 클릭을 해제하시면 취소가 됩니다."; "lng_will_be_notified" = "메시지 작성시 구성원들에게 알림이 갑니다."; "lng_wont_be_notified" = "메시지 작성시 구성원들에게 알림이 가지 않습니다."; @@ -721,29 +726,29 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_user_typing" = "{user}님이 입력중입니다."; "lng_users_typing" = "{user}님과 {second_user}님이 입력중입니다."; "lng_many_typing" = "{count:_not_used_|#명이|#명이} 입력중입니다"; -"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_send_action_record_video" = "비디오 녹화 중"; +"lng_user_action_record_video" = "{user}님이 녹화중입니다"; +"lng_send_action_upload_video" = "비디오 전송 중"; +"lng_user_action_upload_video" = "{user}님이 비디오를 전송 중입니다."; +"lng_send_action_record_audio" = "음송 메시지 녹음 중"; +"lng_user_action_record_audio" = "{user}님이 오디오를 녹음 중입니다"; +"lng_send_action_upload_audio" = "음성 메시지 전송 중"; +"lng_user_action_upload_audio" = "{user}님이 음성 메시지는 전송 중입니다"; +"lng_send_action_upload_photo" = "사진 전송 중"; +"lng_user_action_upload_photo" = "{user}님이 사진을 전송 중입니다"; +"lng_send_action_upload_file" = "파일 전송 중"; +"lng_user_action_upload_file" = "{user}님이 파일을 전송 중입니다"; +"lng_send_action_geo_location" = "위치 선택 중"; +"lng_user_action_geo_location" = "{user}님이 위치를 선택 중입닏"; +"lng_send_action_choose_contact" = "연락처 선택 중"; +"lng_user_action_choose_contact" = "{user}님이 연락처를 선택 중입니다"; "lng_unread_bar" = "{count:_not_used_|#개의 읽지 않은 메시지|#개의 읽지 않은 메시지}"; "lng_maps_point" = "위치"; "lng_save_photo" = "사진 저장"; -"lng_save_video" = "Save video file"; -"lng_save_audio_file" = "Save audio file"; -"lng_save_audio" = "Save voice message"; +"lng_save_video" = "동영상 저장"; +"lng_save_audio_file" = "음성파일 저장"; +"lng_save_audio" = "음성 메시지 저장"; "lng_save_file" = "파일 저장"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; @@ -760,7 +765,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_context_copy_email" = "이메일 복사"; "lng_context_copy_hashtag" = "해시태그 복사"; "lng_context_copy_mention" = "아이디 복사"; -"lng_context_save_image" = "Save Image As..."; +"lng_context_save_image" = "이미지를 다른 이름으로 저장.."; "lng_context_forward_image" = "이미지 전달"; "lng_context_delete_image" = "이미지 삭제"; "lng_context_copy_image" = "이미지 복사"; @@ -768,12 +773,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_context_cancel_download" = "다운로드 취소"; "lng_context_show_in_folder" = "탐색기에서 보기"; "lng_context_show_in_finder" = "탐색기에서 보기"; -"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_save_video" = "비디오를 다른 이름으로 저장..."; +"lng_context_save_audio_file" = "음성파일을 다른 이름으로 저장..."; +"lng_context_save_audio" = "음성메시지를 다른 이름으로 저장..."; "lng_context_pack_info" = "팩 정보"; "lng_context_pack_add" = "스티커 추가"; -"lng_context_save_file" = "Save File As..."; +"lng_context_save_file" = "파일을 다른 이름으로 저장..."; "lng_context_forward_file" = "파일 전달"; "lng_context_delete_file" = "파일 삭제"; "lng_context_close_file" = "파일 닫기"; @@ -800,7 +805,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_send_image_too_large" = "파일이 1.5GB 보다 큼으로 전송 할 수 없습니다 :("; "lng_send_folder" = " «{name}»은 폴더이기 때문에 전송 할 수 없습니다 :("; -"lng_forward_choose" = "Choose recipient..."; +"lng_forward_choose" = "수신자를 선택.."; "lng_forward_cant" = "이쪽으로 전달 할 수 없습니다 :("; "lng_forward_confirm" = "{recipient} 님에게 전달하시겠습니까?"; "lng_forward_share_contact" = "{recipient} 님에게 연락처를 공유하시겠습니까?"; @@ -810,6 +815,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forward_messages" = "{count:_not_used_|전달받은 메시지|# 개의 전달받은 메시지}"; "lng_forwarding_from" = "{user} 님과 {count:_not_used_|# 명|# 명}"; "lng_forwarding_from_two" = "{user} 님과 {second_user}"; +"lng_inline_switch_choose" = "대화 선택..."; +"lng_inline_switch_cant" = "죄송합니다. 이쪽으로 글을 쓸 수 없습니다 :("; "lng_share_cant" = "이쪽으로 공유 할 수 없습니다 :("; "lng_reply_cant" = "죄송합니다. 슈퍼그룹방의 이전 메시지에 답글을 할 수 없습니다 :("; @@ -863,7 +870,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_search_global_results" = "아이디 검색 결과"; "lng_media_save_progress" = "{ready} / {total} {mb}"; -"lng_mediaview_save_as" = "Save As..."; +"lng_mediaview_save_as" = " 다른 이름으로 저장..."; "lng_mediaview_copy" = "복사하기"; "lng_mediaview_forward" = "전달"; "lng_mediaview_delete" = "삭제"; @@ -886,7 +893,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "텔레그램 데스크탑은 {version} 버전으로 업데이트 되었습니다.\n\n{changes}\n\n전체 버전 히스토리는 아래에서 확인 가능합니다:\n{link}"; "lng_new_version_minor" = "— 버그 수정 및 일부 기능 향상"; -"lng_new_version_text" = "공개 그룹, 메시지 고정, 5,000명\n\n— 그룹은 5,000명까지 가능 (기존 1,000명)\n— 모든 그룹은 구성원 크기에 상관 없이 슈퍼그룹으로 변환 가능\n\n슈퍼그룹 관리기능 추가:\n\n— 공개링크를 생성하여 그룹공개 가능 - 누구나 참여하여 대화가능\n— 메시지를 고정하여 중요한 내용을 표시하고 모두에게 알림\n— 여러 메시지를 선택하여 삭제, 스팸신고, 차단 혹은 특정 유저에게 메시지 삭제 가능\n\n자세한 사항:\n{link}"; +"lng_new_version_text" = "봇 API 2.0\n\n— 봇 API 2.0을 소개합니다, 2015년 6월 이후로 가장 큰 봇 플랫폼 업데이트입니다.\n— 봇을 활용하여 기존 송신한 메시지에 대한 업데이트가 가능합니다.\n— 새로운 Inline 키보드를 소개합니다, 콜백, URL열기 혹은 inline모드 전환 버튼으로 매끄러운 인터페이스를 도와줍니다.\n— Inline 봇은 텔레그램에서 활용 가능한 모든 첨부파일을 전송 할 수 있습니다. (비디오, 음악, 스티커, 파일등)\n— 샘플 봇을 활용하여 미리 업데이트 기능을 활용해보세요 : @music, @sticker, @youtoube, @foursquare\n\n봇에 대한 자세한 설명: {link}"; "lng_menu_insert_unicode" = "유니코드 문자를 입력하세요."; diff --git a/Telegram/SourceFiles/langs/lang_nl.strings b/Telegram/Resources/langs/lang_nl.strings similarity index 97% rename from Telegram/SourceFiles/langs/lang_nl.strings rename to Telegram/Resources/langs/lang_nl.strings index 635693b07..ca8a27d0a 100644 --- a/Telegram/SourceFiles/langs/lang_nl.strings +++ b/Telegram/Resources/langs/lang_nl.strings @@ -601,6 +601,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "Antwoord op"; +"lng_bot_share_location_unavailable" = "Sorry, locatie delen is nog niet beschikbaar via Telegram Desktop."; +"lng_bot_inline_geo_unavailable" = "Deze bot heeft je locatie nodig, dit is\nnog niet beschikbaar via Telegram Desktop."; +"lng_bot_share_phone" = "Telefoonnummer delen?"; +"lng_bot_share_phone_confirm" = "Delen"; + "lng_attach_failed" = "Mislukt"; "lng_attach_file" = "Bestand"; "lng_attach_photo" = "Foto"; @@ -663,10 +668,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_count" = "{count:Laden..|# sticker|# stickers}"; "lng_in_dlg_photo" = "Foto"; -"lng_in_dlg_video" = "Video file"; -"lng_in_dlg_audio_file" = "Audio file"; +"lng_in_dlg_video" = "Video"; +"lng_in_dlg_audio_file" = "Audiobestand"; "lng_in_dlg_contact" = "Contact"; -"lng_in_dlg_audio" = "Voice message"; +"lng_in_dlg_audio" = "Spraakbericht"; "lng_in_dlg_file" = "Bestand"; "lng_in_dlg_sticker" = "Sticker"; "lng_in_dlg_sticker_emoji" = "{emoji} (sticker)"; @@ -741,9 +746,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_maps_point" = "Locatie"; "lng_save_photo" = "Afbeelding opslaan"; -"lng_save_video" = "Save video file"; -"lng_save_audio_file" = "Save audio file"; -"lng_save_audio" = "Save voice message"; +"lng_save_video" = "Video opslaan"; +"lng_save_audio_file" = "Audio opslaan"; +"lng_save_audio" = "Spraakbericht opslaan"; "lng_save_file" = "Bestand opslaan"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; @@ -768,9 +773,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_context_cancel_download" = "Download annuleren"; "lng_context_show_in_folder" = "Weergeven in map"; "lng_context_show_in_finder" = "Weergeven in Finder"; -"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_save_video" = "Video opslaan als..."; +"lng_context_save_audio_file" = "Video opslaan als..."; +"lng_context_save_audio" = "Spraakbericht opslaan als..."; "lng_context_pack_info" = "Bundelinformatie"; "lng_context_pack_add" = "Stickers toevoegen"; "lng_context_save_file" = "Bestand opslaan als..."; @@ -810,6 +815,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forward_messages" = "{count:_not_used_|Doorgestuurd bericht|# doorgestuurde berichten}"; "lng_forwarding_from" = "{user} en {count:_not_used_|# andere|# anderen}"; "lng_forwarding_from_two" = "{user} en {second_user}"; +"lng_inline_switch_choose" = "Kies chat..."; +"lng_inline_switch_cant" = "Sorry, je kunt hier niets delen :("; "lng_share_cant" = "Sorry, delen hierheen kan niet :("; "lng_reply_cant" = "Sorry, je kunt geen oude berichten beantwoorden in een supergroep :("; @@ -886,7 +893,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram is bijgewerkt naar versie {version}\n\n{changes} \n\nVolledige versiegeschiedenis is hier te vinden:\n{link}"; "lng_new_version_minor" = "— Probleemoplossing en andere kleine verbeteringen"; -"lng_new_version_text" = "PUBLIEKE GROEPEN, BERICHTEN VASTZETTEN, 5000 LEDEN\n\n— Ledenlimiet voor iedere groepsvorm opgehoogd naar 5000 (voorheen 1000).\n— Iedere groep met een willekeurig aantal leden kan nu worden opgewaardeerd naar een supergroep.\n\nNieuwe functies voor beheerders van supergroepen:\n\n— Maak je groep openbaar door een publieke link in te stellen - iedereen kan de chat zien en er lid van worden.\n— Zet berichten vast om belangrijke informatie weer te geven en alle leden te informeren.\n— Selecteer berichten om te verwijderen, ze als spam te melden, gebruikers te blokkeren of om alle berichten van bepaalde gebruikers ineens te verwijderen.\n\nMeer informatie over deze update:\n{link}"; +"lng_new_version_text" = "BOTS 2.0\n\n— Maak kennis met BOT API 2.0, onze grootste update voor het bot-platform sinds juni 2015.\n— Nieuwe Inline-keyboards met terugroep-functie, 'Open URL' of 'omschakelen naar inline'-knoppen voor een nog betere integratie.\n— Bots kunnen nu berichten bijwerken, direct tijdens je interactie met hen.\n— Inline-bots kunnen nu alle bijlagen sturen die worden ondersteund door Telegram (video's, muziek, stickers, bestanden, etc.).\n— Probeer deze voorbeeldbots uit voor een voorproefje: @music, @sticker, @youtube, @foursquare\n\nMeer over de nieuwe bots: {link}"; "lng_menu_insert_unicode" = "Unicode-besturingsteken invoegen"; diff --git a/Telegram/SourceFiles/langs/lang_pt_BR.strings b/Telegram/Resources/langs/lang_pt_BR.strings similarity index 97% rename from Telegram/SourceFiles/langs/lang_pt_BR.strings rename to Telegram/Resources/langs/lang_pt_BR.strings index 2113785ed..074af6ab9 100644 --- a/Telegram/SourceFiles/langs/lang_pt_BR.strings +++ b/Telegram/Resources/langs/lang_pt_BR.strings @@ -601,6 +601,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "Em resposta a"; +"lng_bot_share_location_unavailable" = "O compartilhamento de localização está atualmente indisponível no Telegram Desktop."; +"lng_bot_inline_geo_unavailable" = "Esse bot requer compartilhamento de localização\nIsso não está disponível no Telegram Desktop."; +"lng_bot_share_phone" = "Compartilhar Número de Telefone?"; +"lng_bot_share_phone_confirm" = "Compartilhar"; + "lng_attach_failed" = "Falhou"; "lng_attach_file" = "Arquivo"; "lng_attach_photo" = "Foto"; @@ -810,6 +815,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forward_messages" = "{count:_not_used_|Mensagem encaminhada|# mensagens encaminhadas}"; "lng_forwarding_from" = "{user} e {count:_not_used_|# outro|# outros}"; "lng_forwarding_from_two" = "{user} e {second_user}"; +"lng_inline_switch_choose" = "Escolher conversa..."; +"lng_inline_switch_cant" = "Desculpe, não há como escrever aqui :("; "lng_share_cant" = "Não há como compartilhar aqui :("; "lng_reply_cant" = "Desculpe, não há como responder uma mensagem antiga no supergrupo :("; @@ -886,7 +893,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram Desktop foi atualizado para a versão {version}\n\n{changes}\n\nHistórico completo de mudanças disponível aqui:\n{link}"; "lng_new_version_minor" = "— Resolução de bugs e outras melhorias menores"; -"lng_new_version_text" = "GRUPOS PÚBLICOS, POSTS FIXADOS, 5.000 MEMBROS\n\n— Grupos agora podem ter até 5.000 membros (mais que 1.000)\n— Grupos de qualquer tamanho podem ser convertidos a supergrupos\n\nNovas ferramentas para administradores dos supergrupos:\n\n— Torne seu grupo público configurando um link público - qualquer um poderá ver a conversa e entrar nela\n— Fixe mensagens para manter as atualizações mais importantes visíveis e notificar todos os membros\n— Selecione várias mensagens para apagar, reporte por spam, bloqueie usuários ou remova todas as mensagens de certos usuários\n\nMais sobre essa atualização:\n{link}"; +"lng_new_version_text" = "BOTS 2.0\n\n— Apresentamos a API para bots 2.0, a maior atualização de nossa plataforma para bots desde junho de 2015.\n— A partir de agora, os bots podem atualizar as mensagens existentes em tempo real, assim como interagir com elas.\n— Os novos teclados integrados com callback, botões para 'Abrir URL' ou 'Alterar para modo integrado', ajudam a criar uma interface fluída.\n— Os bots integrados agora podem enviar qualquer tipo de anexo suportado no Telegram (vídeos, músicas, stickers, arquivos, etc.).\n— Tente usar os bots de exemplos para ver o que está por vir: @music, @sticker, @youtube, @foursquare\n\nMais em: {link}"; "lng_menu_insert_unicode" = "Inserir caractere de controle Unicode"; diff --git a/Telegram/SourceFiles/qmime/freedesktop.org.xml b/Telegram/Resources/qmime/freedesktop.org.xml similarity index 100% rename from Telegram/SourceFiles/qmime/freedesktop.org.xml rename to Telegram/Resources/qmime/freedesktop.org.xml diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index 5ada019a8..626846aac 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -886,6 +886,8 @@ dlgPaddingVer: 8px; dlgHeight: 62px; dlgPhotoPadding: 12px; +dlgImportantHeight: 37px; + noContactsHeight: 100px; noContactsFont: font(fsize); noContactsColor: #777; @@ -950,8 +952,9 @@ dlgUnreadColor: #FFF; dlgUnreadBG: #6fc766; dlgUnreadMutedBG: #bbb; dlgUnreadFont: font(12px bold); +dlgUnreadHeight: 19px; +dlgUnreadTop: 1px; dlgUnreadPaddingHor: 5px; -dlgUnreadPaddingVer: 1px; dlgUnreadRadius: 2px; dlgBG: #FFF; dlgHoverBG: #f5f5f5; @@ -1052,8 +1055,8 @@ msgMinWidth: 190px; msgPhotoSize: 33px; msgPhotoSkip: 40px; msgPadding: margins(13px, 7px, 13px, 8px); -msgMargin: margins(13px, 6px, 53px, 2px); -msgMarginTopAttached: 2px; +msgMargin: margins(13px, 10px, 53px, 2px); +msgMarginTopAttached: 3px; msgLnkPadding: 2px; // for media open / save links msgBorder: #f0f0f0; msgInBg: #fff; @@ -1085,11 +1088,27 @@ msgInReplyBarColor: #2fa9e2; msgOutReplyBarSelColor: #4da79f; msgInReplyBarSelColor: #2fa9e2; +msgBotKbDuration: 200; +msgBotKbFont: semiboldFont; +msgBotKbOverOpacity: 0.1; +msgBotKbIconPadding: 2px; +msgBotKbUrlIcon: sprite(188px, 338px, 10px, 10px); +//msgBotKbRequestPhoneIcon: msgBotKbUrlIcon; +//msgBotKbRequestLocationIcon: msgBotKbUrlIcon; +msgBotKbSwitchPmIcon: sprite(188px, 348px, 10px, 10px); +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, 9px, 80px, 5px); +msgServiceMargin: margins(10px, 10px, 80px, 2px); msgColor: #000; msgDateColor: #000; @@ -1473,6 +1492,11 @@ replyCancel: iconedButton(btnDefIconed) { width: 49px; height: 49px; } +inlineBotCancel: iconedButton(replyCancel) { + height: 46px; + iconPos: point(-1px, 16px); // < 0 means draw in the center of the button + downIconPos: point(-1px, 17px); +} forwardIcon: sprite(368px, 197px, 24px, 24px); historyScroll: flatScroll(scrollDef) { @@ -1527,6 +1551,7 @@ reportSpamBg: #fffffff0; newMsgSound: ':/gui/art/newmsg.wav'; unreadBarHeight: 32px; +unreadBarMargin: 8px; unreadBarFont: semiboldFont; unreadBarBG: #fcfbfa; unreadBarBorder: shadowColor; @@ -2097,16 +2122,16 @@ 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; + height: 38px; + textTop: 9px; downTextTop: 9px; } botKbTinyButton: botKeyboardButton { @@ -2114,12 +2139,17 @@ botKbTinyButton: botKeyboardButton { padding: 3px; height: 25px; textTop: 2px; - downTextTop: 3px; + downTextTop: 2px; } botKbScroll: flatScroll(solidScroll) { deltax: 3px; width: 10px; } +switchPmButton: BoxButton(defaultBoxButton) { + width: 320px; + height: 34px; + textTop: 7px; +} minPhotoSize: 100px; maxMediaSize: 420px; @@ -2460,7 +2490,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; @@ -2469,6 +2499,8 @@ inlineDescriptionFg: #8a8a8a; inlineRowMargin: 6px; inlineRowBorder: linksBorder; inlineRowBorderFg: linksBorderFg; +inlineRowFileNameTop: 2px; +inlineRowFileDescriptionTop: 23px; inlineResultsMinWidth: 64px; inlineDurationMargin: 3px; @@ -2476,3 +2508,17 @@ editTextArea: InputArea(defaultInputArea) { textMargins: margins(1px, 6px, 1px, 4px); heightMax: 256px; } + +toastFont: normalFont; +toastMaxWidth: 480px; +toastMinMargin: 13px; +toastBg: medviewSaveMsg; +toastFg: #FFF; +toastPadding: margins(19px, 13px, 19px, 12px); +toastFadeInDuration: 200; +toastFadeOutDuration: 1000; + +infoButton: PeerAvatarButton { + size: topBarHeight; + photoSize: 42px; +} diff --git a/Telegram/Resources/style_classes.txt b/Telegram/Resources/style_classes.txt index 229b3398b..a3405a048 100644 --- a/Telegram/Resources/style_classes.txt +++ b/Telegram/Resources/style_classes.txt @@ -405,3 +405,8 @@ InputField { iconSprite: sprite; iconPosition: point; } + +PeerAvatarButton { + size: number; + photoSize: number; +} diff --git a/Telegram/SourceFiles/telegram.qrc b/Telegram/Resources/telegram.qrc similarity index 100% rename from Telegram/SourceFiles/telegram.qrc rename to Telegram/Resources/telegram.qrc diff --git a/Telegram/SourceFiles/telegram_emojis.qrc b/Telegram/Resources/telegram_emojis.qrc similarity index 100% rename from Telegram/SourceFiles/telegram_emojis.qrc rename to Telegram/Resources/telegram_emojis.qrc diff --git a/Telegram/SourceFiles/telegram_linux.qrc b/Telegram/Resources/telegram_linux.qrc similarity index 100% rename from Telegram/SourceFiles/telegram_linux.qrc rename to Telegram/Resources/telegram_linux.qrc diff --git a/Telegram/SourceFiles/telegram_mac.qrc b/Telegram/Resources/telegram_mac.qrc similarity index 100% rename from Telegram/SourceFiles/telegram_mac.qrc rename to Telegram/Resources/telegram_mac.qrc diff --git a/Telegram/SourceFiles/telegram_wnd.qrc b/Telegram/Resources/telegram_wnd.qrc similarity index 100% rename from Telegram/SourceFiles/telegram_wnd.qrc rename to Telegram/Resources/telegram_wnd.qrc diff --git a/Telegram/Setup.iss b/Telegram/Setup.iss index c3773c30c..9793ed439 100644 --- a/Telegram/Setup.iss +++ b/Telegram/Setup.iss @@ -25,10 +25,10 @@ DefaultGroupName={#MyAppName} AllowNoIcons=yes OutputDir=.\..\Win32\Deploy OutputBaseFilename=tsetup.{#MyAppVersionFull} -SetupIconFile=.\SourceFiles\art\icon256.ico +SetupIconFile=.\Resources\art\icon256.ico UninstallDisplayIcon={app}\Telegram.exe Compression=lzma -SolidCompression=yes +SolidCompression=yes DisableStartupPrompt=yes PrivilegesRequired=lowest VersionInfoVersion={#MyAppVersion}.0 diff --git a/Telegram/SourceFiles/Telegram.plist b/Telegram/SourceFiles/Telegram.plist deleted file mode 100644 index d8286c37a..000000000 --- a/Telegram/SourceFiles/Telegram.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - NSPrincipalClass - NSApplication - CFBundleIconFile - - CFBundlePackageType - APPL - CFBundleGetInfoString - Created by Qt/QMake - CFBundleSignature - ???? - CFBundleExecutable - Telegram - CFBundleIdentifier - com.yourcompany.${PRODUCT_NAME:rfc1034identifier} - NOTE - This file was generated by Qt/QMake. - - diff --git a/Telegram/SourceFiles/_other/genemoji.cpp b/Telegram/SourceFiles/_other/genemoji.cpp index 22e3fd1bd..544da56bc 100644 --- a/Telegram/SourceFiles/_other/genemoji.cpp +++ b/Telegram/SourceFiles/_other/genemoji.cpp @@ -1944,7 +1944,7 @@ to link the code of portions of this program with the OpenSSL library.\n\ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\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"; + tcpp << "#include \"stdafx.h\"\n#include \"ui/emoji_config.h\"\n\n"; tcpp << "namespace {\n"; // namespace with data tcpp << "\tEmojiData *emojis = 0;\n"; diff --git a/Telegram/SourceFiles/_other/genlang.cpp b/Telegram/SourceFiles/_other/genlang.cpp index 614f81de5..3026fb6bf 100644 --- a/Telegram/SourceFiles/_other/genlang.cpp +++ b/Telegram/SourceFiles/_other/genlang.cpp @@ -432,7 +432,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ QMap > &countedTags(keysCounted[keysOrder[i]]); if (!countedTags.isEmpty()) { for (QMap >::const_iterator j = countedTags.cbegin(), e = countedTags.cend(); j != e; ++j) { - const QVector &counted(*j); + const auto &counted(*j); for (int k = 0, s = counted.size(); k < s; ++k) { th << "\t" << keysOrder[i] << "__" + j.key() + QString::number(k).toUtf8() << ",\n"; } @@ -510,7 +510,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ QMap > &countedTags(keysCounted[keysOrder[i]]); if (!countedTags.isEmpty()) { for (QMap >::const_iterator j = countedTags.cbegin(), e = countedTags.cend(); j != e; ++j) { - const QVector &counted(*j); + const auto &counted(*j); for (int k = 0, s = counted.size(); k < s; ++k) { tcpp << "\t\t\"" << keysOrder[i] << "__" + j.key() + QString::number(k).toUtf8() << "\",\n"; } @@ -534,7 +534,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ QMap > &countedTags(keysCounted[keysOrder[i]]); if (!countedTags.isEmpty()) { for (QMap >::const_iterator j = countedTags.cbegin(), e = countedTags.cend(); j != e; ++j) { - const QVector &counted(*j); + const auto &counted(*j); for (int k = 0, s = counted.size(); k < s; ++k) { writeCppKey(tcpp, keysOrder[i] + "__" + j.key() + QString::number(k).toUtf8(), counted[k]); } diff --git a/Telegram/SourceFiles/_other/genstyles.cpp b/Telegram/SourceFiles/_other/genstyles.cpp index 67f58191f..3d52dc82c 100644 --- a/Telegram/SourceFiles/_other/genstyles.cpp +++ b/Telegram/SourceFiles/_other/genstyles.cpp @@ -383,7 +383,7 @@ to link the code of portions of this program with the OpenSSL library.\n\ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\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 \"ui/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); @@ -1064,7 +1064,7 @@ ScalarValue prepareFont(int variant, const string &name, const char *&text, cons string size, family; int flags = 0; - bool sizepx; + bool sizepx = false; readStyleGenToken(text, end, type, token); if (type != stConsStart) throw Exception(QString("Unexpected token %1 (%2) while reading font() cons!").arg(type).arg(token.c_str())); @@ -1544,7 +1544,7 @@ to link the code of portions of this program with the OpenSSL library.\n\ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\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 \"ui/style.h\"\n\nnamespace st {\n"; tcpp << "\ /*\n\ Created from \'/Resources/style.txt\' by \'/MetaStyle\' project\n\ diff --git a/Telegram/SourceFiles/_other/memain.cpp b/Telegram/SourceFiles/_other/memain.cpp index aaf5b6341..81b1692ca 100644 --- a/Telegram/SourceFiles/_other/memain.cpp +++ b/Telegram/SourceFiles/_other/memain.cpp @@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "memain.h" int main(int argc, char *argv[]) { - QString emoji_in("./SourceFiles/art/emojisprite_"), emoji_out("./SourceFiles/gui/emoji_config.cpp"), emoji_png("./SourceFiles/art/emoji"); + QString emoji_in("./Resources/art/emojisprite_"), emoji_out("./SourceFiles/gui/emoji_config.cpp"), emoji_png("./Resources/art/emoji"); for (int i = 0; i < argc; ++i) { if (string("-emoji_in") == argv[i]) { if (++i < argc) emoji_in = argv[i]; diff --git a/Telegram/SourceFiles/_other/msmain.cpp b/Telegram/SourceFiles/_other/msmain.cpp index ac85f52e4..c59c7dbc4 100644 --- a/Telegram/SourceFiles/_other/msmain.cpp +++ b/Telegram/SourceFiles/_other/msmain.cpp @@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include int main(int argc, char *argv[]) { - QString classes_in("style_classes.txt"), classes_out("style_classes.h"), styles_in("style.txt"), styles_out("style_auto.h"), path_to_sprites("./SourceFiles/art/"); + QString classes_in("style_classes.txt"), classes_out("style_classes.h"), styles_in("style.txt"), styles_out("style_auto.h"), path_to_sprites("./Resources/art/"); for (int i = 0; i < argc; ++i) { if (string("-classes_in") == argv[i]) { if (++i < argc) classes_in = argv[i]; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 720e37ccc..62adaec33 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -19,11 +19,11 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" +#include "ui/style.h" #include "lang.h" #include "application.h" -#include "window.h" +#include "mainwindow.h" #include "mainwidget.h" #include "apiwrap.h" @@ -99,21 +99,21 @@ void ApiWrap::resolveMessageDatas() { 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()); + const auto &d(msgs.c_messages_messages()); App::feedUsers(d.vusers); App::feedChats(d.vchats); App::feedMsgs(d.vmessages, NewMessageExisting); } break; case mtpc_messages_messagesSlice: { - const MTPDmessages_messagesSlice &d(msgs.c_messages_messagesSlice()); + const auto &d(msgs.c_messages_messagesSlice()); App::feedUsers(d.vusers); App::feedChats(d.vchats); App::feedMsgs(d.vmessages, NewMessageExisting); } break; case mtpc_messages_channelMessages: { - const MTPDmessages_channelMessages &d(msgs.c_messages_channelMessages()); + const auto &d(msgs.c_messages_channelMessages()); if (channel) { channel->ptsReceived(d.vpts.v); } else { @@ -169,8 +169,8 @@ void ApiWrap::processFullPeer(PeerData *peer, const MTPUserFull &result) { } void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mtpRequestId req) { - const MTPDmessages_chatFull &d(result.c_messages_chatFull()); - const QVector &vc(d.vchats.c_vector().v); + const auto &d(result.c_messages_chatFull()); + const auto &vc(d.vchats.c_vector().v); bool badVersion = false; if (peer->isChat()) { badVersion = (!vc.isEmpty() && vc.at(0).type() == mtpc_chat && vc.at(0).c_chat().vversion.v < peer->asChat()->version); @@ -186,13 +186,13 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt LOG(("MTP Error: bad type in gotChatFull for chat: %1").arg(d.vfull_chat.type())); return; } - const MTPDchatFull &f(d.vfull_chat.c_chatFull()); + const auto &f(d.vfull_chat.c_chatFull()); App::feedParticipants(f.vparticipants, false, false); - const QVector &v(f.vbot_info.c_vector().v); + const auto &v(f.vbot_info.c_vector().v); for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i < e; ++i) { switch (i->type()) { case mtpc_botInfo: { - const MTPDbotInfo &b(i->c_botInfo()); + const auto &b(i->c_botInfo()); UserData *user = App::userLoaded(b.vuser_id.v); if (user) { user->setBotInfo(*i); @@ -218,7 +218,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt LOG(("MTP Error: bad type in gotChatFull for channel: %1").arg(d.vfull_chat.type())); return; } - const MTPDchannelFull &f(d.vfull_chat.c_channelFull()); + const auto &f(d.vfull_chat.c_channelFull()); PhotoData *photo = App::feedPhoto(f.vchat_photo); ChannelData *channel = peer->asChannel(); channel->flagsFull = f.vflags.v; @@ -245,7 +245,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt if (!h->isEmpty()) { h->clear(true); } - if (hto->inChatList() && h->inChatList()) { + if (hto->inChatList(Dialogs::Mode::All) && h->inChatList(Dialogs::Mode::All)) { App::removeDialog(h); } } @@ -257,11 +257,11 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt App::main()->peerUpdated(cfrom); } } - const QVector &v(f.vbot_info.c_vector().v); + const auto &v(f.vbot_info.c_vector().v); for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i < e; ++i) { switch (i->type()) { case mtpc_botInfo: { - const MTPDbotInfo &b(i->c_botInfo()); + const auto &b(i->c_botInfo()); UserData *user = App::userLoaded(b.vuser_id.v); if (user) { user->setBotInfo(*i); @@ -321,7 +321,7 @@ 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()); + const auto &d(result.c_userFull()); App::feedUsers(MTP_vector(1, d.vuser), false); if (d.has_profile_photo()) { App::feedPhoto(d.vprofile_photo); @@ -351,7 +351,7 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestI } bool ApiWrap::gotPeerFullFailed(PeerData *peer, const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _fullPeerRequests.remove(peer); return true; @@ -399,7 +399,7 @@ void ApiWrap::requestLastParticipants(ChannelData *peer, bool fromStart) { if ((needAdmins && adminsOutdated) || peer->lastParticipantsCountOutdated()) { fromStart = true; } - QMap::iterator i = _participantsRequests.find(peer); + auto i = _participantsRequests.find(peer); if (i != _participantsRequests.cend()) { if (fromStart && i.value() < 0) { // was not loading from start _participantsRequests.erase(i); @@ -420,7 +420,7 @@ 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); + const auto &v(result.c_messages_chats().vchats.c_vector().v); bool badVersion = false; if (peer->isChat()) { badVersion = (!v.isEmpty() && v.at(0).type() == mtpc_chat && v.at(0).c_chat().vversion.v < peer->asChat()->version); @@ -458,7 +458,7 @@ void ApiWrap::gotUsers(const MTPVector &result) { } bool ApiWrap::gotPeerFailed(PeerData *peer, const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _peerRequests.remove(peer); return true; @@ -491,8 +491,8 @@ void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelP peer->mgInfo->lastParticipantsStatus = MegagroupInfo::LastParticipantsUpToDate; } - const MTPDchannels_channelParticipants &d(result.c_channels_channelParticipants()); - const QVector &v(d.vparticipants.c_vector().v); + const auto &d(result.c_channels_channelParticipants()); + const auto &v(d.vparticipants.c_vector().v); App::feedUsers(d.vusers); bool added = false, needBotsInfos = false; int32 botStatus = peer->mgInfo->botStatus; @@ -555,7 +555,7 @@ void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelP } bool ApiWrap::lastParticipantsFail(ChannelData *peer, const RPCError &error, mtpRequestId req) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; if (_participantsRequests.value(peer) == req || _participantsRequests.value(peer) == -req) { _participantsRequests.remove(peer); } else if (_botsRequests.value(peer) == req) { @@ -578,27 +578,27 @@ void ApiWrap::gotSelfParticipant(ChannelData *channel, const MTPchannels_Channel return; } - const MTPDchannels_channelParticipant &p(result.c_channels_channelParticipant()); + const auto &p(result.c_channels_channelParticipant()); App::feedUsers(p.vusers); switch (p.vparticipant.type()) { case mtpc_channelParticipantSelf: { - const MTPDchannelParticipantSelf &d(p.vparticipant.c_channelParticipantSelf()); + const auto &d(p.vparticipant.c_channelParticipantSelf()); channel->inviter = d.vinviter_id.v; channel->inviteDate = date(d.vdate); } break; case mtpc_channelParticipantCreator: { - const MTPDchannelParticipantCreator &d(p.vparticipant.c_channelParticipantCreator()); + const auto &d(p.vparticipant.c_channelParticipantCreator()); channel->inviter = MTP::authedId(); channel->inviteDate = date(MTP_int(channel->date)); } break; case mtpc_channelParticipantModerator: { - const MTPDchannelParticipantModerator &d(p.vparticipant.c_channelParticipantModerator()); + const auto &d(p.vparticipant.c_channelParticipantModerator()); channel->inviter = d.vinviter_id.v; channel->inviteDate = date(d.vdate); } break; case mtpc_channelParticipantEditor: { - const MTPDchannelParticipantEditor &d(p.vparticipant.c_channelParticipantEditor()); + const auto &d(p.vparticipant.c_channelParticipantEditor()); channel->inviter = d.vinviter_id.v; channel->inviteDate = date(d.vdate); } break; @@ -609,7 +609,7 @@ void ApiWrap::gotSelfParticipant(ChannelData *channel, const MTPchannels_Channel } bool ApiWrap::gotSelfParticipantFail(ChannelData *channel, const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; if (error.type() == qstr("USER_NOT_PARTICIPANT")) { channel->inviter = -1; @@ -655,7 +655,7 @@ void ApiWrap::kickParticipantDone(KickRequest kick, const MTPUpdates &result, mt } bool ApiWrap::kickParticipantFail(KickRequest kick, const RPCError &error, mtpRequestId req) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _kickRequests.remove(kick); return true; } @@ -680,10 +680,10 @@ 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()); + const auto &d(result.c_messages_stickerSet()); if (d.vset.type() != mtpc_stickerSet) return; - const MTPDstickerSet &s(d.vset.c_stickerSet()); + const auto &s(d.vset.c_stickerSet()); Stickers::Sets &sets(Global::RefStickerSets()); auto it = sets.find(setId); @@ -695,7 +695,7 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) it->title = stickerSetTitle(s); it->flags = s.vflags.v; - const QVector &d_docs(d.vdocuments.c_vector().v); + const auto &d_docs(d.vdocuments.c_vector().v); auto custom = sets.find(Stickers::CustomSetId); StickerPack pack; @@ -735,13 +735,13 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) } else { it->stickers = pack; it->emoji.clear(); - const QVector &v(d.vpacks.c_vector().v); + const auto &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()); + const auto &pack(v.at(i).c_stickerPack()); if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) { - const QVector &stickers(pack.vdocuments.c_vector().v); + const auto &stickers(pack.vdocuments.c_vector().v); StickerPack p; p.reserve(stickers.size()); for (int32 j = 0, c = stickers.size(); j < c; ++j) { @@ -765,7 +765,7 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) } bool ApiWrap::gotStickerSetFail(uint64 setId, const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _stickerSetRequests.remove(setId); return true; @@ -861,21 +861,21 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs const QVector *v = 0; switch (msgs.type()) { case mtpc_messages_messages: { - const MTPDmessages_messages &d(msgs.c_messages_messages()); + const auto &d(msgs.c_messages_messages()); App::feedUsers(d.vusers); App::feedChats(d.vchats); v = &d.vmessages.c_vector().v; } break; case mtpc_messages_messagesSlice: { - const MTPDmessages_messagesSlice &d(msgs.c_messages_messagesSlice()); + const auto &d(msgs.c_messages_messagesSlice()); App::feedUsers(d.vusers); App::feedChats(d.vchats); v = &d.vmessages.c_vector().v; } break; case mtpc_messages_channelMessages: { - const MTPDmessages_channelMessages &d(msgs.c_messages_channelMessages()); + const auto &d(msgs.c_messages_channelMessages()); if (channel) { channel->ptsReceived(d.vpts.v); } else { @@ -894,7 +894,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs if (!v) return; QMap msgsIds; // copied from feedMsgs for (int32 i = 0, l = v->size(); i < l; ++i) { - const MTPMessage &msg(v->at(i)); + const auto &msg(v->at(i)); switch (msg.type()) { case mtpc_message: msgsIds.insert((uint64(uint32(msg.c_message().vid.v)) << 32) | uint64(i), i); break; case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i); break; diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 3ea5eca0f..8845d90bd 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -28,7 +28,7 @@ public: ApiWrap(QObject *parent); void init(); - typedef SharedCallback2 RequestMessageDataCallback; + typedef SharedCallback RequestMessageDataCallback; void requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback *callback); void requestFullPeer(PeerData *peer); @@ -69,7 +69,7 @@ private: struct MessageDataRequest { MessageDataRequest() : req(0) { } - typedef SharedCallback2::Ptr CallbackPtr; + typedef SharedCallback::Ptr CallbackPtr; typedef QList Callbacks; mtpRequestId req; Callbacks callbacks; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 4887c2681..c227c243b 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -19,8 +19,10 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "lang.h" +#include "app.h" +#include "lang.h" +#include "dialogs/dialogs_layout.h" #include "audio.h" #include "application.h" #include "fileuploader.h" @@ -29,7 +31,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include #endif #include "localstorage.h" - +#include "apiwrap.h" #include "numbers.h" namespace { @@ -55,12 +57,6 @@ namespace { typedef QHash WebPagesData; WebPagesData webPagesData; - typedef QMap ReplyMarkups; - ReplyMarkups replyMarkups; - ReplyMarkup zeroMarkup(qFlags(MTPDreplyKeyboardMarkup_ClientFlag::f_zero)); - typedef QMap ChannelReplyMarkups; - ChannelReplyMarkups channelReplyMarkups; - PhotoItems photoItems; DocumentItems documentItems; WebPageItems webPageItems; @@ -111,9 +107,6 @@ namespace { typedef QHash LastPhotosMap; LastPhotosMap lastPhotosMap; - typedef QMap InlineResultLoaders; - InlineResultLoaders inlineResultLoaders; - style::color _msgServiceBg; style::color _msgServiceSelectBg; style::color _historyScrollBarColor; @@ -153,23 +146,29 @@ namespace App { return AppClass::app(); } - Window *wnd() { + MainWindow *wnd() { return AppClass::wnd(); } MainWidget *main() { - Window *w(wnd()); - return w ? w->mainWidget() : 0; + if (auto w = wnd()) { + return w->mainWidget(); + } + return nullptr; } SettingsWidget *settings() { - Window *w(wnd()); - return w ? w->settingsWidget() : 0; + if (auto w = wnd()) { + return w->settingsWidget(); + } + return nullptr; } bool passcoded() { - Window *w(wnd()); - return w ? w->passcodeWidget() : 0; + if (auto w = wnd()) { + return w->passcodeWidget(); + } + return false; } FileUploader *uploader() { @@ -180,21 +179,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 (auto w = wnd()) { w->tempDirDelete(Local::ClearManagerAll); w->notifyClearFast(); w->setupIntro(true); } - MainWidget *m(main()); - if (m) m->destroyData(); MTP::authed(0); Local::reset(); @@ -205,13 +202,14 @@ namespace App { globalNotifyChatsPtr = UnknownNotifySettings; if (App::uploader()) App::uploader()->clear(); clearStorageImages(); - if (w) { + if (auto w = wnd()) { w->getTitle()->updateBackButton(); w->updateTitleStatus(); w->getTitle()->resizeEvent(0); } return true; } +} // namespace void logOut() { if (MTP::started()) { @@ -366,16 +364,16 @@ namespace App { UserData *feedUsers(const MTPVector &users, bool emitPeerUpdated) { UserData *data = 0; - const QVector &v(users.c_vector().v); + const auto &v(users.c_vector().v); for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - const MTPuser &user(*i); + const auto &user(*i); data = 0; bool wasContact = false, minimal = false; const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty(); switch (user.type()) { case mtpc_userEmpty: { - const MTPDuserEmpty &d(user.c_userEmpty()); + const auto &d(user.c_userEmpty()); PeerId peer(peerFromUser(d.vid.v)); data = App::user(peer); @@ -391,7 +389,7 @@ namespace App { data->contact = -1; } break; case mtpc_user: { - const MTPDuser &d(user.c_user()); + const auto &d(user.c_user()); minimal = d.is_min(); PeerId peer(peerFromUser(d.vid.v)); @@ -437,13 +435,17 @@ 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::setSelfUsername(uname); + SignalHandlers::setCrashAnnotation("Username", uname); } data->setName(fname, lname, pname, uname); if (d.has_photo()) { @@ -520,14 +522,14 @@ namespace App { PeerData *feedChats(const MTPVector &chats, bool emitPeerUpdated) { PeerData *data = 0; - const QVector &v(chats.c_vector().v); + const auto &v(chats.c_vector().v); for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - const MTPchat &chat(*i); + const auto &chat(*i); data = 0; bool minimal = false; switch (chat.type()) { case mtpc_chat: { - const MTPDchat &d(chat.c_chat()); + const auto &d(chat.c_chat()); data = App::chat(peerFromChat(d.vid.v)); data->input = MTP_inputPeerChat(d.vid); @@ -539,7 +541,7 @@ namespace App { cdata->date = d.vdate.v; if (d.has_migrated_to() && d.vmigrated_to.type() == mtpc_inputChannel) { - const MTPDinputChannel &c(d.vmigrated_to.c_inputChannel()); + const auto &c(d.vmigrated_to.c_inputChannel()); ChannelData *channel = App::channel(peerFromChannel(c.vchannel_id)); if (!channel->mgInfo) { channel->flags |= MTPDchannel::Flag::f_megagroup; @@ -561,7 +563,7 @@ namespace App { if (!h->isEmpty()) { h->clear(true); } - if (hto->inChatList() && h->inChatList()) { + if (hto->inChatList(Dialogs::Mode::All) && h->inChatList(Dialogs::Mode::All)) { App::removeDialog(h); } } @@ -586,7 +588,7 @@ namespace App { } } break; case mtpc_chatForbidden: { - const MTPDchatForbidden &d(chat.c_chatForbidden()); + const auto &d(chat.c_chatForbidden()); data = App::chat(peerFromChat(d.vid.v)); data->input = MTP_inputPeerChat(d.vid); @@ -602,7 +604,7 @@ namespace App { cdata->isForbidden = true; } break; case mtpc_channel: { - const MTPDchannel &d(chat.c_channel()); + const auto &d(chat.c_channel()); PeerId peer(peerFromChannel(d.vid.v)); minimal = d.is_min(); @@ -642,7 +644,7 @@ namespace App { cdata->setPhoto(d.vphoto); } break; case mtpc_channelForbidden: { - const MTPDchannelForbidden &d(chat.c_channelForbidden()); + const auto &d(chat.c_channelForbidden()); PeerId peer(peerFromChannel(d.vid.v)); data = App::channel(peer); @@ -684,18 +686,18 @@ namespace App { ChatData *chat = 0; switch (p.type()) { case mtpc_chatParticipantsForbidden: { - const MTPDchatParticipantsForbidden &d(p.c_chatParticipantsForbidden()); + const auto &d(p.c_chatParticipantsForbidden()); chat = App::chat(d.vchat_id.v); chat->count = -1; chat->invalidateParticipants(); } break; case mtpc_chatParticipants: { - const MTPDchatParticipants &d(p.c_chatParticipants()); + const auto &d(p.c_chatParticipants()); chat = App::chat(d.vchat_id.v); if (!requestBotInfos || chat->version <= d.vversion.v) { // !requestBotInfos is true on getFullChat result chat->version = d.vversion.v; - const QVector &v(d.vparticipants.c_vector().v); + const auto &v(d.vparticipants.c_vector().v); chat->count = v.size(); int32 pversion = chat->participants.isEmpty() ? 1 : (chat->participants.begin().value() + 1); chat->invitedByMe = ChatData::InvitedByMe(); @@ -705,17 +707,17 @@ namespace App { int32 uid = 0, inviter = 0; switch (i->type()) { case mtpc_chatParticipantCreator: { - const MTPDchatParticipantCreator &p(i->c_chatParticipantCreator()); + const auto &p(i->c_chatParticipantCreator()); uid = p.vuser_id.v; chat->creator = uid; } break; case mtpc_chatParticipantAdmin: { - const MTPDchatParticipantAdmin &p(i->c_chatParticipantAdmin()); + const auto &p(i->c_chatParticipantAdmin()); uid = p.vuser_id.v; inviter = p.vinviter_id.v; } break; case mtpc_chatParticipant: { - const MTPDchatParticipant &p(i->c_chatParticipant()); + const auto &p(i->c_chatParticipant()); uid = p.vuser_id.v; inviter = p.vinviter_id.v; } break; @@ -965,7 +967,7 @@ 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->updateMedia(m.has_media() ? (&m.vmedia) : 0); + existing->updateMedia(m.has_media() ? (&m.vmedia) : nullptr); existing->setViewsCount(m.has_views() ? m.vviews.v : -1); existing->addToOverview(AddToOverviewNew); @@ -985,15 +987,7 @@ namespace App { peerId = peerFromUser(m.vfrom_id); } 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->updateMedia(m.has_media() ? (&m.vmedia) : 0, true); - existing->setViewsCount(m.has_views() ? m.vviews.v : -1); - if (existing->history()->textCachedFor == existing) { - existing->history()->textCachedFor = 0; - } - if (App::main()) { - App::main()->dlgUpdated(existing->history(), existing->id); - } + existing->applyEdition(m); } } @@ -1027,10 +1021,10 @@ namespace App { void feedMsgs(const QVector &msgs, NewMessageType type) { QMap msgsIds; for (int32 i = 0, l = msgs.size(); i < l; ++i) { - const MTPMessage &msg(msgs.at(i)); + const auto &msg(msgs.at(i)); switch (msg.type()) { case mtpc_message: { - const MTPDmessage &d(msg.c_message()); + const auto &d(msg.c_message()); bool needToAdd = true; if (type == NewMessageUnread) { // new message, index my forwarded messages to links overview if (checkEntitiesAndViewsUpdate(d)) { // already in blocks @@ -1058,17 +1052,17 @@ namespace App { ImagePtr image(const MTPPhotoSize &size) { switch (size.type()) { case mtpc_photoSize: { - const MTPDphotoSize &d(size.c_photoSize()); + const auto &d(size.c_photoSize()); if (d.vlocation.type() == mtpc_fileLocation) { - const MTPDfileLocation &l(d.vlocation.c_fileLocation()); + const auto &l(d.vlocation.c_fileLocation()); return ImagePtr(StorageImageLocation(d.vw.v, d.vh.v, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v), d.vsize.v); } } break; case mtpc_photoCachedSize: { - const MTPDphotoCachedSize &d(size.c_photoCachedSize()); + const auto &d(size.c_photoCachedSize()); if (d.vlocation.type() == mtpc_fileLocation) { - const MTPDfileLocation &l(d.vlocation.c_fileLocation()); - const string &s(d.vbytes.c_string().v); + const auto &l(d.vlocation.c_fileLocation()); + const auto &s(d.vbytes.c_string().v); QByteArray bytes(s.data(), s.size()); return ImagePtr(StorageImageLocation(d.vw.v, d.vh.v, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v), bytes); } else if (d.vlocation.type() == mtpc_fileLocationUnavailable) { @@ -1083,7 +1077,7 @@ namespace App { StorageImageLocation imageLocation(int32 w, int32 h, const MTPFileLocation &loc) { if (loc.type() == mtpc_fileLocation) { - const MTPDfileLocation &l(loc.c_fileLocation()); + const auto &l(loc.c_fileLocation()); return StorageImageLocation(w, h, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v); } return StorageImageLocation(w, h, 0, 0, 0, 0); @@ -1092,11 +1086,11 @@ namespace App { StorageImageLocation imageLocation(const MTPPhotoSize &size) { switch (size.type()) { case mtpc_photoSize: { - const MTPDphotoSize &d(size.c_photoSize()); + const auto &d(size.c_photoSize()); return imageLocation(d.vw.v, d.vh.v, d.vlocation); } break; case mtpc_photoCachedSize: { - const MTPDphotoCachedSize &d(size.c_photoCachedSize()); + const auto &d(size.c_photoCachedSize()); return imageLocation(d.vw.v, d.vh.v, d.vlocation); } break; } @@ -1149,8 +1143,8 @@ namespace App { } else { if (channelHistory) { channelHistory->messageWithIdDeleted(i->v); - if (channelHistory->unreadCount > 0 && i->v >= channelHistory->inboxReadBefore) { - channelHistory->setUnreadCount(channelHistory->unreadCount - 1); + if (channelHistory->unreadCount() > 0 && i->v >= channelHistory->inboxReadBefore) { + channelHistory->setUnreadCount(channelHistory->unreadCount() - 1); } } } @@ -1163,9 +1157,9 @@ namespace App { } void feedUserLinks(const MTPVector &links, bool emitPeerUpdated) { - const QVector &v(links.c_vector().v); + const auto &v(links.c_vector().v); for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - const MTPDcontacts_link &dv(i->c_contacts_link()); + const auto &dv(i->c_contacts_link()); UserData *user = feedUsers(MTP_vector(1, dv.vuser), false); MTPint userId(MTP_int(0)); switch (dv.vuser.type()) { @@ -1298,7 +1292,7 @@ namespace App { } switch (photo.type()) { case mtpc_photo: { - const MTPDphoto &ph(photo.c_photo()); + const auto &ph(photo.c_photo()); return App::photoSet(ph.vid.v, 0, ph.vaccess_hash.v, ph.vdate.v, ImagePtr(*thumb, "JPG"), ImagePtr(*medium, "JPG"), ImagePtr(*full, "JPG")); } break; case mtpc_photoEmpty: return App::photo(photo.c_photoEmpty().vid.v); @@ -1307,7 +1301,7 @@ namespace App { } PhotoData *feedPhoto(const MTPDphoto &photo, PhotoData *convert) { - const QVector &sizes(photo.vsizes.c_vector().v); + const auto &sizes(photo.vsizes.c_vector().v); const MTPPhotoSize *thumb = 0, *medium = 0, *full = 0; int32 thumbLevel = -1, mediumLevel = -1, fullLevel = -1; for (QVector::const_iterator i = sizes.cbegin(), e = sizes.cend(); i != e; ++i) { @@ -1362,7 +1356,7 @@ namespace App { DocumentData *feedDocument(const MTPdocument &document, const QPixmap &thumb) { switch (document.type()) { case mtpc_document: { - const MTPDdocument &d(document.c_document()); + const auto &d(document.c_document()); return App::documentSet(d.vid.v, 0, d.vaccess_hash.v, d.vdate.v, d.vattributes.c_vector().v, qs(d.vmime_type), ImagePtr(thumb, "JPG"), d.vdc_id.v, d.vsize.v, StorageImageLocation()); } break; case mtpc_documentEmpty: return App::document(document.c_documentEmpty().vid.v); @@ -1450,12 +1444,12 @@ namespace App { 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(); + for_const (PeerData *peer, peersData) { + if (!peer->userName().compare(uname, Qt::CaseInsensitive)) { + return peer; } } - return 0; + return nullptr; } void updateImage(ImagePtr &old, ImagePtr now) { @@ -1535,7 +1529,7 @@ namespace App { DocumentData *document(const DocumentId &document) { DocumentsData::const_iterator i = ::documentsData.constFind(document); if (i == ::documentsData.cend()) { - i = ::documentsData.insert(document, new DocumentData(document)); + i = ::documentsData.insert(document, DocumentData::create(document)); } return i.value(); } @@ -1543,45 +1537,44 @@ namespace App { 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) { bool sentSticker = false; if (convert) { + MediaKey oldKey = convert->mediaKey(); if (convert->id != document) { DocumentsData::iterator i = ::documentsData.find(convert->id); if (i != ::documentsData.cend() && i.value() == convert) { ::documentsData.erase(i); } - // 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() != 0); } if (date) { - convert->access = access; - convert->date = date; convert->setattributes(attributes); + convert->setRemoteLocation(dc, access); + convert->date = date; convert->mime = mime; if (!thumb->isNull() && (convert->thumb->isNull() || convert->thumb->width() < thumb->width() || convert->thumb->height() < thumb->height())) { updateImage(convert->thumb, thumb); } - convert->dc = dc; convert->size = size; convert->recountIsImage(); if (convert->sticker() && convert->sticker()->loc.isNull() && !thumbLocation.isNull()) { convert->sticker()->loc = thumbLocation; } + + MediaKey newKey = convert->mediaKey(); + if (newKey != oldKey) { + if (convert->voice()) { + Local::copyAudio(oldKey, newKey); + } else if (convert->sticker() || convert->isAnimation()) { + Local::copyStickerImage(oldKey, newKey); + } + } } if (cSavedGifs().indexOf(convert) >= 0) { // id changed Local::writeSavedGifs(); } - - const FileLocation &loc(convert->location(true)); - if (!loc.isEmpty()) { - Local::writeFileLocation(convert->mediaKey(), loc); - } } DocumentsData::const_iterator i = ::documentsData.constFind(document); DocumentData *result; @@ -1589,7 +1582,11 @@ namespace App { if (convert) { result = convert; } else { - result = new DocumentData(document, access, date, attributes, mime, thumb, dc, size); + result = DocumentData::create(document, dc, access, attributes); + result->date = date; + result->mime = mime; + result->thumb = thumb; + result->size = size; result->recountIsImage(); if (result->sticker()) { result->sticker()->loc = thumbLocation; @@ -1599,14 +1596,15 @@ namespace App { } else { result = i.value(); if (result != convert && date) { - result->access = access; - result->date = date; result->setattributes(attributes); + if (!result->isValid()) { + result->setRemoteLocation(dc, access); + } + result->date = date; result->mime = mime; if (!thumb->isNull() && (result->thumb->isNull() || result->thumb->width() < thumb->width() || result->thumb->height() < thumb->height())) { result->thumb = thumb; } - result->dc = dc; result->size = size; result->recountIsImage(); if (result->sticker() && result->sticker()->loc.isNull() && !thumbLocation.isNull()) { @@ -1628,7 +1626,7 @@ namespace App { return i.value(); } - WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &type, 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) { + WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const QString &description, PhotoData *photo, DocumentData *document, int32 duration, const QString &author, int32 pendingTill) { if (convert) { if (convert->id != webPage) { WebPagesData::iterator i = webPagesData.find(convert->id); @@ -1645,7 +1643,7 @@ namespace App { convert->title = title; convert->description = description; convert->photo = photo; - convert->doc = doc; + convert->document = document; convert->duration = duration; convert->author = author; if (convert->pendingTill > 0 && pendingTill <= 0 && api()) api()->clearWebPageRequest(convert); @@ -1659,7 +1657,7 @@ namespace App { if (convert) { result = convert; } else { - result = new WebPageData(webPage, toWebPageType(type), url, displayUrl, siteName, title, description, photo, doc, duration, author, (pendingTill >= -1) ? pendingTill : -1); + result = new WebPageData(webPage, toWebPageType(type), url, displayUrl, siteName, title, description, document, photo, duration, author, (pendingTill >= -1) ? pendingTill : -1); if (pendingTill > 0 && api()) { api()->requestWebPageDelayed(result); } @@ -1676,7 +1674,7 @@ namespace App { result->title = title; result->description = description; result->photo = photo; - result->doc = doc; + result->document = document; result->duration = duration; result->author = author; if (result->pendingTill > 0 && pendingTill <= 0 && api()) api()->clearWebPageRequest(result); @@ -1712,7 +1710,7 @@ namespace App { MTPPhoto photoFromUserPhoto(MTPint userId, MTPint date, const MTPUserProfilePhoto &photo) { if (photo.type() == mtpc_userProfilePhoto) { - const MTPDuserProfilePhoto &uphoto(photo.c_userProfilePhoto()); + const auto &uphoto(photo.c_userProfilePhoto()); QVector photoSizes; photoSizes.push_back(MTP_photoSize(MTP_string("a"), uphoto.vphoto_small, MTP_int(160), MTP_int(160), MTP_int(0))); @@ -1744,14 +1742,16 @@ namespace App { } HistoryItem *histItemById(ChannelId channelId, MsgId itemId) { - MsgsData *data = fetchMsgsData(channelId, false); - if (!data) return 0; + if (!itemId) return nullptr; - MsgsData::const_iterator i = data->constFind(itemId); + MsgsData *data = fetchMsgsData(channelId, false); + if (!data) return nullptr; + + auto i = data->constFind(itemId); if (i != data->cend()) { return i.value(); } - return 0; + return nullptr; } void historyRegItem(HistoryItem *item) { @@ -1851,8 +1851,6 @@ namespace App { } ::hoveredItem = ::pressedItem = ::hoveredLinkItem = ::pressedLinkItem = ::contextItem = 0; - replyMarkups.clear(); - channelReplyMarkups.clear(); } void historyClearItems() { @@ -1863,20 +1861,20 @@ namespace App { cSetSavedPeers(SavedPeers()); cSetSavedPeersByTime(SavedPeersByTime()); cSetRecentInlineBots(RecentInlineBots()); - for (PeersData::const_iterator i = peersData.cbegin(), e = peersData.cend(); i != e; ++i) { - delete *i; + 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 (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(); @@ -2020,6 +2018,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); @@ -2048,8 +2047,8 @@ namespace App { } void clearHistories() { - textlnkOver(TextLinkPtr()); - textlnkDown(TextLinkPtr()); + ClickHandler::clearActive(); + ClickHandler::unpressed(); histories().clear(); @@ -2083,6 +2082,8 @@ namespace App { mainEmojiMap.clear(); otherEmojiMap.clear(); + Dialogs::Layout::clearStyleSheets(); + clearAllImages(); } @@ -2343,7 +2344,7 @@ namespace App { GifItems gifs = ::gifItems; for (GifItems::const_iterator i = gifs.cbegin(), e = gifs.cend(); i != e; ++i) { if (HistoryMedia *media = i.value()->getMedia()) { - media->stopInline(i.value()); + media->stopInline(); } } } @@ -2388,106 +2389,6 @@ 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(mtpCastFlags(d.vflags.v) | MTPDreplyKeyboardMarkup_ClientFlag::f_zero)); - } - } break; - - case mtpc_replyKeyboardForceReply: { - const MTPDreplyKeyboardForceReply &d(markup.c_replyKeyboardForceReply()); - insertReplyMarkup(channelId, msgId, ReplyMarkup(mtpCastFlags(d.vflags.v) | MTPDreplyKeyboardMarkup_ClientFlag::f_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()); diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index ebe1711e5..4e437b71b 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -20,10 +20,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "types.h" +#include "core/basic_types.h" class AppClass; -class Window; +class MainWindow; class MainWidget; class SettingsWidget; class ApiWrap; @@ -44,19 +44,11 @@ typedef QHash GifItems; typedef QHash PhotosData; typedef QHash DocumentsData; -struct ReplyMarkup { - ReplyMarkup(MTPDreplyKeyboardMarkup::Flags flags = 0) : flags(flags) { - } - typedef QList > Commands; - Commands commands; - MTPDreplyKeyboardMarkup::Flags flags; -}; - class LayeredWidget; namespace App { AppClass *app(); - Window *wnd(); + MainWindow *wnd(); MainWidget *main(); SettingsWidget *settings(); bool passcoded(); @@ -64,7 +56,6 @@ namespace App { ApiWrap *api(); void logOut(); - bool loggedOut(); QString formatPhone(QString phone); @@ -268,14 +259,6 @@ 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(); diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index f84770840..a5d4a7334 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "application.h" -#include "style.h" +#include "ui/style.h" #include "shortcuts.h" @@ -690,7 +690,7 @@ AppClass::AppClass() : QObject() cSetLang(languageDefault); } } else if (cLang() > languageDefault && cLang() < languageCount) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[cLang()] + qsl(".strings")); + 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()) { @@ -718,7 +718,7 @@ AppClass::AppClass() : QObject() QMimeDatabase().mimeTypeForName(qsl("text/plain")); // create mime database - _window = new Window(); + _window = new MainWindow(); _window->createWinId(); _window->init(); @@ -835,7 +835,7 @@ void AppClass::chatPhotoCleared(PeerId peer, const MTPUpdates &updates) { void AppClass::selfPhotoDone(const MTPphotos_Photo &result) { if (!App::self()) return; - const MTPDphotos_photo &photo(result.c_photos_photo()); + const auto &photo(result.c_photos_photo()); App::feedPhoto(photo.vphoto); App::feedUsers(photo.vusers); cancelPhotoUpdate(App::self()->id); @@ -851,7 +851,7 @@ void AppClass::chatPhotoDone(PeerId peer, const MTPUpdates &updates) { } bool AppClass::peerPhotoFail(PeerId peer, const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; LOG(("Application Error: update photo failed %1: %2").arg(error.type()).arg(error.description())); cancelPhotoUpdate(peer); @@ -962,6 +962,15 @@ void AppClass::onSwitchDebugMode() { } } +void AppClass::onSwitchWorkMode() { + Global::SetDialogsModeEnabled(!Global::DialogsModeEnabled()); + Global::SetDialogsMode(Dialogs::Mode::All); + Local::writeUserSettings(); + cSetRestarting(true); + cSetRestartingToSettings(true); + App::quit(); +} + void AppClass::onSwitchTestMode() { if (cTestMode()) { QFile(cWorkingDir() + qsl("tdata/withtestmode")).remove(); @@ -1025,12 +1034,11 @@ void AppClass::checkMapVersion() { if (Local::oldMapVersion() < AppVersion) { if (Local::oldMapVersion()) { QString versionFeatures; - if ((cDevVersion() || cBetaVersion()) && Local::oldMapVersion() < 9035) { -// QString ctrl = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? qsl("Cmd") : qsl("Ctrl"); - versionFeatures = QString::fromUtf8("\xe2\x80\x94 Design improvements\n\xe2\x80\x94 Bug fixes and other minor improvements");// .replace('@', qsl("@") + QChar(0x200D)); -// versionFeatures = lng_new_version_text(lt_link, qsl("https://telegram.org/blog/supergroups5k")).trimmed(); - } else if (Local::oldMapVersion() < 9031) { - versionFeatures = lng_new_version_text(lt_link, qsl("https://telegram.org/blog/supergroups5k")).trimmed(); + if ((cDevVersion() || cBetaVersion()) && Local::oldMapVersion() < 9041) { +// versionFeatures = QString::fromUtf8("\xe2\x80\x94 Design improvements\n\xe2\x80\x94 Bug fixes and other minor improvements"); + versionFeatures = langNewVersionText(); + } else if (Local::oldMapVersion() < 9041) { + versionFeatures = langNewVersionText(); } else { versionFeatures = lang(lng_new_version_minor).trimmed(); } @@ -1040,15 +1048,12 @@ void AppClass::checkMapVersion() { } } } - if (cNeedConfigResave()) { - Local::writeUserSettings(); - } } AppClass::~AppClass() { Shortcuts::finish(); - if (Window *w = _window) { + if (auto w = _window) { _window = 0; delete w; } @@ -1081,7 +1086,7 @@ AppClass *AppClass::app() { return AppObject; } -Window *AppClass::wnd() { +MainWindow *AppClass::wnd() { return AppObject ? AppObject->_window : 0; } diff --git a/Telegram/SourceFiles/application.h b/Telegram/SourceFiles/application.h index 3e0a37e56..774ebc85b 100644 --- a/Telegram/SourceFiles/application.h +++ b/Telegram/SourceFiles/application.h @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "window.h" +#include "mainwindow.h" #include "pspecific.h" class UpdateChecker; @@ -153,7 +153,7 @@ public: ~AppClass(); static AppClass *app(); - static Window *wnd(); + static MainWindow *wnd(); static MainWidget *main(); FileUploader *uploader(); @@ -195,6 +195,7 @@ public slots: void photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file); void onSwitchDebugMode(); + void onSwitchWorkMode(); void onSwitchTestMode(); void killDownloadSessions(); @@ -211,7 +212,7 @@ private: uint64 _lastActionTime; - Window *_window; + MainWindow *_window; FileUploader *_uploader; Translator *_translator; diff --git a/Telegram/SourceFiles/audio.cpp b/Telegram/SourceFiles/audio.cpp index 29205c7af..49a536ed1 100644 --- a/Telegram/SourceFiles/audio.cpp +++ b/Telegram/SourceFiles/audio.cpp @@ -506,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; diff --git a/Telegram/SourceFiles/audio.h b/Telegram/SourceFiles/audio.h index f9259906e..17bbf8cf0 100644 --- a/Telegram/SourceFiles/audio.h +++ b/Telegram/SourceFiles/audio.h @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "types.h" +#include "core/basic_types.h" void audioInit(); bool audioWorks(); diff --git a/Telegram/SourceFiles/boxes/aboutbox.cpp b/Telegram/SourceFiles/boxes/aboutbox.cpp index 87e01f6a3..083d6b650 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.cpp +++ b/Telegram/SourceFiles/boxes/aboutbox.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "aboutbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" #include "autoupdater.h" #include "boxes/confirmbox.h" @@ -139,7 +139,7 @@ void AboutBox::dropEvent(QDropEvent *e) { 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) { diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp index e9d1783ad..f721c6b57 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.cpp +++ b/Telegram/SourceFiles/boxes/abstractbox.cpp @@ -25,7 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "abstractbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" void BlueTitleShadow::paintEvent(QPaintEvent *e) { Painter p(this); diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 53d9e5d80..444742a81 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -26,9 +26,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "contactsbox.h" #include "confirmbox.h" #include "photocropbox.h" -#include "gui/filedialog.h" +#include "ui/filedialog.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" +#include "apiwrap.h" AddContactBox::AddContactBox(QString fname, QString lname, QString phone) : AbstractBox(st::boxWidth) , _user(0) @@ -199,7 +200,7 @@ void AddContactBox::onSave() { } bool AddContactBox::onSaveUserFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _addRequest = 0; QString err(error.type()); @@ -220,13 +221,13 @@ bool AddContactBox::onSaveUserFail(const RPCError &error) { void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) { if (isHidden() || !App::main()) return; - const MTPDcontacts_importedContacts &d(res.c_contacts_importedContacts()); + const auto &d(res.c_contacts_importedContacts()); App::feedUsers(d.vusers); - const QVector &v(d.vimported.c_vector().v); + const auto &v(d.vimported.c_vector().v); UserData *user = nullptr; if (!v.isEmpty()) { - const MTPDimportedContact &c(v.front().c_importedContact()); + const auto &c(v.front().c_importedContact()); if (c.vclient_id.v != _contactId) return; user = App::userLoaded(c.vuser_id.v); @@ -246,7 +247,7 @@ void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) { } void AddContactBox::onSaveUserDone(const MTPcontacts_ImportedContacts &res) { - const MTPDcontacts_importedContacts &d(res.c_contacts_importedContacts()); + const auto &d(res.c_contacts_importedContacts()); App::feedUsers(d.vusers); emit closed(); } @@ -530,7 +531,7 @@ void GroupInfoBox::creationDone(const MTPUpdates &updates) { } bool GroupInfoBox::creationFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _creationRequestId = 0; if (error.type() == "NO_CHAT_TITLE") { @@ -903,7 +904,7 @@ void SetupChannelBox::onUpdateDone(const MTPBool &result) { } bool SetupChannelBox::onUpdateFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _saveRequestId = 0; QString err(error.type()); @@ -940,7 +941,7 @@ void SetupChannelBox::onCheckDone(const MTPBool &result) { } bool SetupChannelBox::onCheckFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _checkRequestId = 0; QString err(error.type()); @@ -971,7 +972,7 @@ bool SetupChannelBox::onCheckFail(const RPCError &error) { } bool SetupChannelBox::onFirstCheckFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _checkRequestId = 0; QString err(error.type()); @@ -1128,7 +1129,7 @@ void EditNameTitleBox::onSaveSelfDone(const MTPUser &user) { } bool EditNameTitleBox::onSaveSelfFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; QString err(error.type()); QString first = textOneLine(_first.getLastText().trimmed()), last = textOneLine(_last.getLastText().trimmed()); @@ -1150,7 +1151,7 @@ bool EditNameTitleBox::onSaveSelfFail(const RPCError &error) { } bool EditNameTitleBox::onSaveChatFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _requestId = 0; QString err(error.type()); @@ -1183,7 +1184,7 @@ EditChannelBox::EditChannelBox(ChannelData *channel) : AbstractBox() , _saveTitleRequestId(0) , _saveDescriptionRequestId(0) , _saveSignRequestId(0) { - connect(App::main(), SIGNAL(peerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&)), this, SLOT(peerUpdated(PeerData*))); + connect(App::main(), SIGNAL(peerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&)), this, SLOT(peerUpdated(PeerData*))); setMouseTracking(true); @@ -1334,7 +1335,7 @@ void EditChannelBox::saveSign() { } bool EditChannelBox::onSaveFail(const RPCError &error, mtpRequestId req) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; QString err(error.type()); if (req == _saveTitleRequestId) { diff --git a/Telegram/SourceFiles/boxes/autolockbox.cpp b/Telegram/SourceFiles/boxes/autolockbox.cpp index 3af3b2615..a755697ca 100644 --- a/Telegram/SourceFiles/boxes/autolockbox.cpp +++ b/Telegram/SourceFiles/boxes/autolockbox.cpp @@ -26,7 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "autolockbox.h" #include "confirmbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" AutoLockBox::AutoLockBox() : _close(this, lang(lng_box_ok), st::defaultBoxButton) { diff --git a/Telegram/SourceFiles/boxes/backgroundbox.cpp b/Telegram/SourceFiles/boxes/backgroundbox.cpp index e35eb32c9..ddbdf4a39 100644 --- a/Telegram/SourceFiles/boxes/backgroundbox.cpp +++ b/Telegram/SourceFiles/boxes/backgroundbox.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "backgroundbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" #include "settingswidget.h" BackgroundInner::BackgroundInner() : @@ -42,13 +42,13 @@ void BackgroundInner::gotWallpapers(const MTPVector &result) { App::WallPapers wallpapers; wallpapers.push_back(App::WallPaper(0, ImagePtr(st::msgBG0), ImagePtr(st::msgBG0))); - const QVector &v(result.c_vector().v); + const auto &v(result.c_vector().v); for (int i = 0, l = v.size(); i < l; ++i) { - const MTPWallPaper w(v.at(i)); + const auto &w(v.at(i)); switch (w.type()) { case mtpc_wallPaper: { - const MTPDwallPaper &d(w.c_wallPaper()); - const QVector &sizes(d.vsizes.c_vector().v); + const auto &d(w.c_wallPaper()); + const auto &sizes(d.vsizes.c_vector().v); const MTPPhotoSize *thumb = 0, *full = 0; int32 thumbLevel = -1, fullLevel = -1; for (QVector::const_iterator j = sizes.cbegin(), e = sizes.cend(); j != e; ++j) { @@ -56,14 +56,14 @@ void BackgroundInner::gotWallpapers(const MTPVector &result) { int32 w = 0, h = 0; switch (j->type()) { case mtpc_photoSize: { - const string &s(j->c_photoSize().vtype.c_string().v); + const auto &s(j->c_photoSize().vtype.c_string().v); if (s.size()) size = s[0]; w = j->c_photoSize().vw.v; h = j->c_photoSize().vh.v; } break; case mtpc_photoCachedSize: { - const string &s(j->c_photoCachedSize().vtype.c_string().v); + const auto &s(j->c_photoCachedSize().vtype.c_string().v); if (s.size()) size = s[0]; w = j->c_photoCachedSize().vw.v; h = j->c_photoCachedSize().vh.v; @@ -87,7 +87,7 @@ void BackgroundInner::gotWallpapers(const MTPVector &result) { } break; case mtpc_wallPaperSolid: { - const MTPDwallPaperSolid &d(w.c_wallPaperSolid()); + const auto &d(w.c_wallPaperSolid()); } break; } } diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index 3e379aae0..910243ea0 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -23,9 +23,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "confirmbox.h" #include "mainwidget.h" -#include "window.h" - +#include "mainwindow.h" +#include "apiwrap.h" #include "application.h" +#include "core/click_handler_types.h" TextParseOptions _confirmBoxTextOptions = { TextParseLinks | TextParseMultiline | TextParseRichText, // flags @@ -83,33 +84,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 +117,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); + auto state = _text.getStateLeft(m.x() - st::boxPadding.left(), m.y() - st::boxPadding.top(), _textWidth, width()); textstyleRestore(); - if (_myLink != textlnkOver()) { - if (wasMy || _myLink || rect().contains(m)) { - textlnkOver(_myLink); - } - setCursor(_myLink ? style::cur_pointer : style::cur_default); - update(); - } + + ClickHandler::setActive(state.link, this); } void ConfirmBox::closePressed() { @@ -174,6 +167,16 @@ void ConfirmBox::resizeEvent(QResizeEvent *e) { _cancel.moveToRight(st::boxButtonPadding.right() + _confirm.width() + st::boxButtonPadding.left(), _confirm.y()); } +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())); @@ -181,11 +184,7 @@ ConfirmLinkBox::ConfirmLinkBox(const QString &url) : ConfirmBox(lang(lng_open_th void ConfirmLinkBox::onOpenLink() { Ui::hideLayer(); - if (reMailStart().match(_url).hasMatch()) { - EmailLink(_url).onClick(Qt::LeftButton); - } else { - TextLink(_url).onClick(Qt::LeftButton); - } + UrlClickHandler::doOpen(_url); } MaxInviteBox::MaxInviteBox(const QString &link) : AbstractBox(st::boxWidth) @@ -337,7 +336,7 @@ void ConvertToSupergroupBox::convertDone(const MTPUpdates &updates) { } bool ConvertToSupergroupBox::convertFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; Ui::hideLayer(); return true; } @@ -433,7 +432,7 @@ void PinMessageBox::pinDone(const MTPUpdates &updates) { } bool PinMessageBox::pinFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; Ui::hideLayer(); return true; } diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index 05b8febf3..097e6f436 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -23,7 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #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 diff --git a/Telegram/SourceFiles/boxes/connectionbox.cpp b/Telegram/SourceFiles/boxes/connectionbox.cpp index aa5ebd767..8f2fc96b0 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.cpp +++ b/Telegram/SourceFiles/boxes/connectionbox.cpp @@ -25,7 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "connectionbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" ConnectionBox::ConnectionBox() : AbstractBox(st::boxWidth) , _hostInput(this, st::connectionHostInputField, lang(lng_connection_host_ph), cConnectionProxy().host) @@ -336,7 +336,6 @@ void AutoDownloadBox::onSave() { i.value()->automaticLoadSettingsChanged(); } } - Notify::automaticLoadSettingsChangedGif(); } changed = true; } diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index 3e0cf9212..326c94bef 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -19,70 +19,43 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" +#include "boxes/contactsbox.h" + +#include "dialogs/dialogs_indexed_list.h" #include "lang.h" - -#include "addcontactbox.h" -#include "contactsbox.h" +#include "boxes/addcontactbox.h" +#include "boxes/contactsbox.h" #include "mainwidget.h" -#include "window.h" - +#include "mainwindow.h" #include "application.h" +#include "ui/filedialog.h" +#include "boxes/photocropbox.h" +#include "boxes/confirmbox.h" +#include "apiwrap.h" -#include "gui/filedialog.h" -#include "photocropbox.h" - -#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) -, _newItemSel(false) -, _chat(0) -, _channel(0) -, _membersFilter(MembersFilterRecent) -, _bot(0) , _creating(creating) , _allAdmins(this, lang(lng_chat_all_members_admins), false, st::contactsAdminCheckbox) -, _addToPeer(0) -, _addAdmin(0) -, _addAdminRequestId(0) -, _addAdminBox(0) -, _contacts(&App::main()->contactsList()) -, _sel(0) -, _filteredSel(-1) -, _mouseSel(false) -, _selCount(0) -, _searching(false) -, _byUsernameSel(-1) -, _addContactLnk(this, lang(lng_add_contact_button)) -, _saving(false) { +, _contacts(App::main()->contactsList()) +, _addContactLnk(this, lang(lng_add_contact_button)) { init(); } ContactsInner::ContactsInner(ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already) : TWidget() , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) -, _newItemHeight(0) -, _newItemSel(false) -, _chat(0) , _channel(channel) , _membersFilter(membersFilter) -, _bot(0) , _creating(CreatingGroupChannel) , _already(already) , _allAdmins(this, lang(lng_chat_all_members_admins), false, st::contactsAdminCheckbox) -, _addToPeer(0) -, _addAdmin(0) -, _addAdminRequestId(0) -, _addAdminBox(0) -, _contacts(&App::main()->contactsList()) -, _sel(0) -, _filteredSel(-1) -, _mouseSel(false) -, _selCount(0) -, _searching(false) -, _byUsernameSel(-1) -, _addContactLnk(this, lang(lng_add_contact_button)) -, _saving(false) { +, _contacts(App::main()->contactsList()) +, _addContactLnk(this, lang(lng_add_contact_button)) { init(); } @@ -94,34 +67,19 @@ namespace { ContactsInner::ContactsInner(ChatData *chat, MembersFilter membersFilter) : TWidget() , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) -, _newItemHeight(0) -, _newItemSel(false) , _chat(chat) -, _channel(0) , _membersFilter(membersFilter) -, _bot(0) -, _creating(CreatingGroupNone) , _allAdmins(this, lang(lng_chat_all_members_admins), !_chat->adminsEnabled(), st::contactsAdminCheckbox) , _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right() - st::contactsCheckPosition.x() * 2 - st::contactsCheckIcon.pxWidth()) , _aboutAllAdmins(st::boxTextFont, lang(lng_chat_about_all_admins), _defaultOptions, _aboutWidth) , _aboutAdmins(st::boxTextFont, lang(lng_chat_about_admins), _defaultOptions, _aboutWidth) -, _addToPeer(0) -, _addAdmin(0) -, _addAdminRequestId(0) -, _addAdminBox(0) -, _contacts((membersFilter == MembersFilterRecent) ? (&App::main()->contactsList()) : (new DialogsIndexed(DialogsSortByAdd))) -, _sel(0) -, _filteredSel(-1) -, _mouseSel(false) -, _selCount(0) -, _searching(false) -, _byUsernameSel(-1) -, _addContactLnk(this, lang(lng_add_contact_button)) -, _saving(false) { +, _customList((membersFilter == MembersFilterRecent) ? std_::unique_ptr() : std_::make_unique(Dialogs::SortMode::Add)) +, _contacts((membersFilter == MembersFilterRecent) ? App::main()->contactsList() : _customList.get()) +, _addContactLnk(this, lang(lng_add_contact_button)) { initList(); if (membersFilter == MembersFilterAdmins) { _newItemHeight = st::contactsNewItemHeight + qMax(_aboutAllAdmins.countHeight(_aboutWidth), _aboutAdmins.countHeight(_aboutWidth)) + st::contactsAboutHeight; - if (!_contacts->list.count) { + if (_contacts->isEmpty()) { App::api()->requestFullPeer(_chat); } } @@ -130,33 +88,18 @@ ContactsInner::ContactsInner(ChatData *chat, MembersFilter membersFilter) : TWid ContactsInner::ContactsInner(UserData *bot) : TWidget() , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) -, _newItemHeight(0) -, _newItemSel(false) -, _chat(0) -, _channel(0) -, _membersFilter(MembersFilterRecent) , _bot(bot) -, _creating(CreatingGroupNone) , _allAdmins(this, lang(lng_chat_all_members_admins), false, st::contactsAdminCheckbox) -, _addToPeer(0) -, _addAdmin(0) -, _addAdminRequestId(0) -, _addAdminBox(0) -, _contacts(new DialogsIndexed(DialogsSortByAdd)) -, _sel(0) -, _filteredSel(-1) -, _mouseSel(false) -, _selCount(0) -, _searching(false) -, _byUsernameSel(-1) -, _addContactLnk(this, lang(lng_add_contact_button)) -, _saving(false) { - DialogsIndexed &v(App::main()->dialogsList()); - for (DialogRow *r = v.list.begin; r != v.list.end; r = r->next) { - if (r->history->peer->isChat() && r->history->peer->asChat()->canEdit()) { - _contacts->addToEnd(r->history); - } else if (r->history->peer->isMegagroup() && (r->history->peer->asChannel()->amCreator() || r->history->peer->asChannel()->amEditor())) { - _contacts->addToEnd(r->history); +, _customList(std_::make_unique(Dialogs::SortMode::Add)) +, _contacts(_customList.get()) +, _addContactLnk(this, lang(lng_add_contact_button)) { + auto v = App::main()->dialogsList(); + for_const (auto row, *v) { + auto peer = row->history()->peer; + if (peer->isChat() && peer->asChat()->canEdit()) { + _contacts->addToEnd(row->history()); + } else if (peer->isMegagroup() && (peer->asChannel()->amCreator() || peer->asChannel()->amEditor())) { + _contacts->addToEnd(row->history()); } } init(); @@ -169,14 +112,14 @@ void ContactsInner::init() { setAttribute(Qt::WA_OpaquePaintEvent); - for (DialogRow *r = _contacts->list.begin; r != _contacts->list.end; r = r->next) { - r->attached = 0; + for_const (auto row, _contacts->all()) { + row->attached = nullptr; } _filter = qsl("a"); updateFilter(); - connect(App::main(), SIGNAL(dialogRowReplaced(DialogRow*,DialogRow*)), this, SLOT(onDialogRowReplaced(DialogRow*,DialogRow*))); + connect(App::main(), SIGNAL(dialogRowReplaced(Dialogs::Row*,Dialogs::Row*)), this, SLOT(onDialogRowReplaced(Dialogs::Row*,Dialogs::Row*))); connect(App::main(), SIGNAL(peerUpdated(PeerData*)), this, SLOT(peerUpdated(PeerData *))); connect(App::main(), SIGNAL(peerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&))); connect(App::main(), SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(peerUpdated(PeerData*))); @@ -274,7 +217,7 @@ void ContactsInner::addAdminDone(const MTPUpdates &result, mtpRequestId req) { } bool ContactsInner::addAdminFail(const RPCError &error, mtpRequestId req) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; if (req != _addAdminRequestId) return true; @@ -301,7 +244,7 @@ void ContactsInner::saving(bool flag) { void ContactsInner::peerUpdated(PeerData *peer) { if (_chat && (!peer || peer == _chat)) { bool inited = false; - if (_membersFilter == MembersFilterAdmins && !_contacts->list.count && !_chat->participants.isEmpty()) { + if (_membersFilter == MembersFilterAdmins && _contacts->isEmpty() && !_chat->participants.isEmpty()) { initList(); inited = true; } @@ -312,8 +255,8 @@ void ContactsInner::peerUpdated(PeerData *peer) { delete i.value(); } _contactsData.clear(); - for (DialogRow *row = _contacts->list.begin; row->next; row = row->next) { - row->attached = 0; + for_const (auto row, _contacts->all()) { + row->attached = nullptr; } if (!_filter.isEmpty()) { for (int32 j = 0, s = _filtered.size(); j < s; ++j) { @@ -329,10 +272,10 @@ void ContactsInner::peerUpdated(PeerData *peer) { } else { ContactsData::iterator i = _contactsData.find(peer); if (i != _contactsData.cend()) { - for (DialogRow *row = _contacts->list.begin; row->next; row = row->next) { + for_const (auto row, _contacts->all()) { if (row->attached == i.value()) { - row->attached = 0; - update(0, _newItemHeight + _rowHeight * row->pos, width(), _rowHeight); + row->attached = nullptr; + update(0, _newItemHeight + _rowHeight * row->pos(), width(), _rowHeight); } } if (!_filter.isEmpty()) { @@ -357,14 +300,13 @@ void ContactsInner::loadProfilePhotos(int32 yFrom) { if (yFrom < 0) yFrom = 0; if (_filter.isEmpty()) { - if (_contacts->list.count) { - _contacts->list.adjustCurrent(yFrom - _newItemHeight, _rowHeight); - for ( - DialogRow *preloadFrom = _contacts->list.current; - preloadFrom != _contacts->list.end && (_newItemHeight + preloadFrom->pos * _rowHeight) < yTo; - preloadFrom = preloadFrom->next - ) { - preloadFrom->history->peer->loadUserpic(); + if (!_contacts->isEmpty()) { + auto i = _contacts->cfind(yFrom - _newItemHeight, _rowHeight); + for (auto end = _contacts->cend(); i != end; ++i) { + if ((_newItemHeight + (*i)->pos() * _rowHeight) >= yTo) { + break; + } + (*i)->history()->peer->loadUserpic(); } } } else if (!_filtered.isEmpty()) { @@ -375,16 +317,16 @@ void ContactsInner::loadProfilePhotos(int32 yFrom) { if (to > _filtered.size()) to = _filtered.size(); for (; from < to; ++from) { - _filtered[from]->history->peer->loadUserpic(); + _filtered[from]->history()->peer->loadUserpic(); } } } } -ContactsInner::ContactData *ContactsInner::contactData(DialogRow *row) { +ContactsInner::ContactData *ContactsInner::contactData(Dialogs::Row *row) { ContactData *data = (ContactData*)row->attached; if (!data) { - PeerData *peer = row->history->peer; + PeerData *peer = row->history()->peer; ContactsData::const_iterator i = _contactsData.constFind(peer); if (i == _contactsData.cend()) { _contactsData.insert(peer, data = new ContactData()); @@ -509,7 +451,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) { int32 yFrom = r.y(), yTo = r.y() + r.height(); if (_filter.isEmpty()) { - if (_contacts->list.count || !_byUsername.isEmpty()) { + if (!_contacts->isEmpty() || !_byUsername.isEmpty()) { if (_newItemHeight) { if (_chat) { p.fillRect(0, 0, width(), _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, st::contactsAboutBg); @@ -530,18 +472,18 @@ void ContactsInner::paintEvent(QPaintEvent *e) { yTo -= _newItemHeight; p.translate(0, _newItemHeight); } - if (_contacts->list.count) { - _contacts->list.adjustCurrent(yFrom, _rowHeight); - - DialogRow *drawFrom = _contacts->list.current; - p.translate(0, drawFrom->pos * _rowHeight); - while (drawFrom != _contacts->list.end && drawFrom->pos * _rowHeight < yTo) { - paintDialog(p, drawFrom->history->peer, contactData(drawFrom), (drawFrom == _sel)); + if (!_contacts->isEmpty()) { + auto i = _contacts->cfind(yFrom, _rowHeight); + p.translate(0, (*i)->pos() * _rowHeight); + for (auto end = _contacts->cend(); i != end; ++i) { + if ((*i)->pos() * _rowHeight >= yTo) { + break; + } + paintDialog(p, (*i)->history()->peer, contactData(*i), (*i == _sel)); p.translate(0, _rowHeight); - drawFrom = drawFrom->next; } - yFrom -= _contacts->list.count * _rowHeight; - yTo -= _contacts->list.count * _rowHeight; + yFrom -= _contacts->size() * _rowHeight; + yTo -= _contacts->size() * _rowHeight; } if (!_byUsername.isEmpty()) { p.fillRect(0, 0, width(), st::searchedBarHeight, st::searchedBarBG->b); @@ -605,7 +547,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) { int32 to = ceilclamp(yTo, _rowHeight, 0, _filtered.size()); p.translate(0, from * _rowHeight); for (; from < to; ++from) { - paintDialog(p, _filtered[from]->history->peer, contactData(_filtered[from]), (_filteredSel == from)); + paintDialog(p, _filtered[from]->history()->peer, contactData(_filtered[from]), (_filteredSel == from)); p.translate(0, _rowHeight); } } @@ -640,10 +582,10 @@ void ContactsInner::updateSelectedRow() { update(0, 0, width(), st::contactsNewItemHeight); } if (_sel) { - update(0, _newItemHeight + _sel->pos * _rowHeight, width(), _rowHeight); + update(0, _newItemHeight + _sel->pos() * _rowHeight, width(), _rowHeight); } if (_byUsernameSel >= 0) { - update(0, _newItemHeight + _contacts->list.count * _rowHeight + st::searchedBarHeight + _byUsernameSel * _rowHeight, width(), _rowHeight); + update(0, _newItemHeight + _contacts->size() * _rowHeight + st::searchedBarHeight + _byUsernameSel * _rowHeight, width(), _rowHeight); } } else { if (_filteredSel >= 0) { @@ -734,14 +676,14 @@ void ContactsInner::chooseParticipant() { if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) { peer = _byUsername[_byUsernameSel]; } else if (_sel) { - peer = _sel->history->peer; + peer = _sel->history()->peer; } } else { if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) { peer = _byUsernameFiltered[_byUsernameSel]; } else { if (_filteredSel < 0 || _filteredSel >= _filtered.size()) return; - peer = _filtered[_filteredSel]->history->peer; + peer = _filtered[_filteredSel]->history()->peer; } } if (peer) { @@ -771,8 +713,8 @@ void ContactsInner::chooseParticipant() { update(); } -void ContactsInner::changeCheckState(DialogRow *row) { - changeCheckState(contactData(row), row->history->peer); +void ContactsInner::changeCheckState(Dialogs::Row *row) { + changeCheckState(contactData(row), row->history()->peer); } void ContactsInner::changeCheckState(ContactData *data, PeerData *peer) { @@ -818,8 +760,8 @@ void ContactsInner::updateSel() { } p.setY(p.y() - _newItemHeight); } - DialogRow *newSel = (in && !newItemSel && (p.y() >= 0) && (p.y() < _contacts->list.count * _rowHeight)) ? _contacts->list.rowAtY(p.y(), _rowHeight) : 0; - int32 byUsernameSel = (in && !newItemSel && p.y() >= _contacts->list.count * _rowHeight + st::searchedBarHeight) ? ((p.y() - _contacts->list.count * _rowHeight - st::searchedBarHeight) / _rowHeight) : -1; + Dialogs::Row *newSel = (in && !newItemSel && (p.y() >= 0) && (p.y() < _contacts->size() * _rowHeight)) ? _contacts->rowAtY(p.y(), _rowHeight) : nullptr; + int32 byUsernameSel = (in && !newItemSel && p.y() >= _contacts->size() * _rowHeight + st::searchedBarHeight) ? ((p.y() - _contacts->size() * _rowHeight - st::searchedBarHeight) / _rowHeight) : -1; if (byUsernameSel >= _byUsername.size()) byUsernameSel = -1; if (newSel != _sel || byUsernameSel != _byUsernameSel || newItemSel != _newItemSel) { updateSelectedRow(); @@ -879,23 +821,23 @@ void ContactsInner::updateFilter(QString filter) { _filtered.clear(); if (!f.isEmpty()) { - DialogsList *dialogsToFilter = 0; - if (_contacts->list.count) { + const Dialogs::List *toFilter = nullptr; + if (!_contacts->isEmpty()) { for (fi = fb; fi != fe; ++fi) { - DialogsIndexed::DialogsIndex::iterator i = _contacts->index.find(fi->at(0)); - if (i == _contacts->index.cend()) { - dialogsToFilter = 0; + auto found = _contacts->filtered(fi->at(0)); + if (found->isEmpty()) { + toFilter = nullptr; break; } - if (!dialogsToFilter || dialogsToFilter->count > i.value()->count) { - dialogsToFilter = i.value(); + if (!toFilter || toFilter->size() > found->size()) { + toFilter = found; } } } - if (dialogsToFilter && dialogsToFilter->count) { - _filtered.reserve(dialogsToFilter->count); - for (DialogRow *i = dialogsToFilter->begin, *e = dialogsToFilter->end; i != e; i = i->next) { - const PeerData::Names &names(i->history->peer->names); + if (toFilter) { + _filtered.reserve(toFilter->size()); + for_const (auto row, *toFilter) { + const PeerData::Names &names(row->history()->peer->names); PeerData::Names::const_iterator nb = names.cbegin(), ne = names.cend(), ni; for (fi = fb; fi != fe; ++fi) { QString filterName(*fi); @@ -909,8 +851,8 @@ void ContactsInner::updateFilter(QString filter) { } } if (fi == fe) { - i->attached = 0; - _filtered.push_back(i); + row->attached = nullptr; + _filtered.push_back(row); } } } @@ -964,7 +906,7 @@ void ContactsInner::updateFilter(QString filter) { } } -void ContactsInner::onDialogRowReplaced(DialogRow *oldRow, DialogRow *newRow) { +void ContactsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow) { if (!_filter.isEmpty()) { for (FilteredDialogs::iterator i = _filtered.begin(), e = _filtered.end(); i != e;) { if (*i == oldRow) { // this row is shown in filtered and maybe is in contacts! @@ -987,8 +929,8 @@ void ContactsInner::onDialogRowReplaced(DialogRow *oldRow, DialogRow *newRow) { } } _mouseSel = false; - int32 cnt = (_filter.isEmpty() ? _contacts->list.count : _filtered.size()); - int32 newh = cnt ? (cnt * _rowHeight) : st::noContactsHeight; + int cnt = (_filter.isEmpty() ? _contacts->size() : _filtered.size()); + int newh = cnt ? (cnt * _rowHeight) : st::noContactsHeight; resize(width(), newh); } @@ -1046,9 +988,9 @@ void ContactsInner::refresh() { } else { if (!_allAdmins.isHidden()) _allAdmins.hide(); } - if (_contacts->list.count || !_byUsername.isEmpty()) { + if (!_contacts->isEmpty() || !_byUsername.isEmpty()) { if (!_addContactLnk.isHidden()) _addContactLnk.hide(); - resize(width(), _newItemHeight + (_contacts->list.count * _rowHeight) + (_byUsername.isEmpty() ? 0 : (st::searchedBarHeight + _byUsername.size() * _rowHeight))); + resize(width(), _newItemHeight + (_contacts->size() * _rowHeight) + (_byUsername.isEmpty() ? 0 : (st::searchedBarHeight + _byUsername.size() * _rowHeight))); } else if (_chat && _membersFilter == MembersFilterAdmins) { if (!_addContactLnk.isHidden()) _addContactLnk.hide(); resize(width(), _newItemHeight + st::noContactsHeight); @@ -1097,7 +1039,6 @@ ContactsInner::~ContactsInner() { delete *i; } if (_bot || (_chat && _membersFilter == MembersFilterAdmins)) { - delete _contacts; if (_bot && _bot->botInfo) _bot->botInfo->startGroupToken = QString(); } } @@ -1113,12 +1054,12 @@ void ContactsInner::selectSkip(int32 dir) { if (_filter.isEmpty()) { int cur = 0; if (_sel) { - for (DialogRow *i = _contacts->list.begin; i != _sel; i = i->next) { + for (auto i = _contacts->cbegin(); *i != _sel; ++i) { ++cur; } if (_newItemHeight) ++cur; } else if (_byUsernameSel >= 0) { - cur = (_contacts->list.count + _byUsernameSel); + cur = (_contacts->size() + _byUsernameSel); if (_newItemHeight) ++cur; } else if (!_newItemSel) { cur = -1; @@ -1126,32 +1067,39 @@ void ContactsInner::selectSkip(int32 dir) { cur += dir; if (cur <= 0) { _newItemSel = (_chat && _membersFilter == MembersFilterAdmins) ? false : (_newItemHeight ? true : false); - _sel = (!_newItemHeight && _contacts->list.count) ? _contacts->list.begin : 0; - _byUsernameSel = (!_newItemHeight && !_contacts->list.count && !_byUsername.isEmpty()) ? 0 : -1; - } else if (cur >= _contacts->list.count + (_newItemHeight ? 1 : 0)) { + _sel = (!_newItemHeight && !_contacts->isEmpty()) ? *_contacts->cbegin() : nullptr; + _byUsernameSel = (!_newItemHeight && _contacts->isEmpty() && !_byUsername.isEmpty()) ? 0 : -1; + } else if (cur >= _contacts->size() + (_newItemHeight ? 1 : 0)) { _newItemSel = false; if (_byUsername.isEmpty()) { - _sel = _contacts->list.count ? _contacts->list.end->prev : 0; + _sel = _contacts->isEmpty() ? nullptr : *(_contacts->cend() - 1); _byUsernameSel = -1; } else { - _sel = 0; - _byUsernameSel = cur - _contacts->list.count; + _sel = nullptr; + _byUsernameSel = cur - _contacts->size(); if (_byUsernameSel >= _byUsername.size()) _byUsernameSel = _byUsername.size() - 1; } } else { _newItemSel = false; if (_newItemHeight) --cur; - for (_sel = _contacts->list.begin; cur; _sel = _sel->next) { - --cur; + for (auto i = _contacts->cbegin(); ; ++i) { + _sel = *i; + if (!cur) { + break; + } else { + --cur; + } } _byUsernameSel = -1; } if (dir > 0) { - while (_sel && _sel->next && contactData(_sel)->inchat) { - _sel = _sel->next; + for (auto i = _contacts->cfind(_sel), end = _contacts->cend(); i != end && contactData(*i)->inchat; ++i) { + _sel = *i; } - if (!_sel || !_sel->next) { - _sel = 0; + if (_sel && contactData(_sel)->inchat) { + _sel = nullptr; + } + if (!_sel) { if (!_byUsername.isEmpty()) { if (_byUsernameSel < 0) _byUsernameSel = 0; for (; _byUsernameSel < _byUsername.size() && d_byUsername[_byUsernameSel]->inchat;) { @@ -1165,10 +1113,15 @@ void ContactsInner::selectSkip(int32 dir) { --_byUsernameSel; } if (_byUsernameSel < 0) { - if (_contacts->list.count) { - if (!_newItemSel && !_sel) _sel = _contacts->list.end->prev; - for (; _sel && contactData(_sel)->inchat;) { - _sel = _sel->prev; + if (!_contacts->isEmpty()) { + if (!_newItemSel && !_sel) _sel = *(_contacts->cend() - 1); + if (_sel) { + for (auto i = _contacts->cfind(_sel), b = _contacts->cbegin(); i != b && contactData(*i)->inchat; --i) { + _sel = *i; + } + if (contactData(_sel)->inchat) { + _sel = nullptr; + } } } } @@ -1176,9 +1129,9 @@ void ContactsInner::selectSkip(int32 dir) { if (_newItemSel) { emit mustScrollTo(0, _newItemHeight); } else if (_sel) { - emit mustScrollTo(_newItemHeight + _sel->pos * _rowHeight, _newItemHeight + (_sel->pos + 1) * _rowHeight); + emit mustScrollTo(_newItemHeight + _sel->pos() * _rowHeight, _newItemHeight + (_sel->pos() + 1) * _rowHeight); } else if (_byUsernameSel >= 0) { - emit mustScrollTo(_newItemHeight + (_contacts->list.count + _byUsernameSel) * _rowHeight + st::searchedBarHeight, _newItemHeight + (_contacts->list.count + _byUsernameSel + 1) * _rowHeight + st::searchedBarHeight); + emit mustScrollTo(_newItemHeight + (_contacts->size() + _byUsernameSel) * _rowHeight + st::searchedBarHeight, _newItemHeight + (_contacts->size() + _byUsernameSel + 1) * _rowHeight + st::searchedBarHeight); } } else { int cur = (_filteredSel >= 0) ? _filteredSel : ((_byUsernameSel >= 0) ? (_filtered.size() + _byUsernameSel) : -1); @@ -1239,8 +1192,8 @@ void ContactsInner::selectSkipPage(int32 h, int32 dir) { QVector ContactsInner::selected() { QVector result; - for (DialogRow *row = _contacts->list.begin; row->next; row = row->next) { - if (_checkedContacts.contains(row->history->peer)) { + for_const (auto row, *_contacts) { + if (_checkedContacts.contains(row->history()->peer)) { contactData(row); // fill _contactsData } } @@ -1260,8 +1213,8 @@ QVector ContactsInner::selected() { QVector ContactsInner::selectedInputs() { QVector result; - for (DialogRow *row = _contacts->list.begin; row->next; row = row->next) { - if (_checkedContacts.contains(row->history->peer)) { + for_const (auto row, *_contacts) { + if (_checkedContacts.contains(row->history()->peer)) { contactData(row); // fill _contactsData } } @@ -1280,8 +1233,8 @@ QVector ContactsInner::selectedInputs() { } PeerData *ContactsInner::selectedUser() { - for (DialogRow *row = _contacts->list.begin; row->next; row = row->next) { - if (_checkedContacts.contains(row->history->peer)) { + for_const (auto row, *_contacts) { + if (_checkedContacts.contains(row->history()->peer)) { contactData(row); // fill _contactsData } } @@ -1474,7 +1427,7 @@ void ContactsBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId r } bool ContactsBox::peopleFailed(const RPCError &error, mtpRequestId req) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; if (_peopleRequest == req) { _peopleRequest = 0; @@ -1619,7 +1572,7 @@ void ContactsBox::onCreate() { if (_saveRequestId) return; MTPVector users(MTP_vector(_inner.selectedInputs())); - const QVector &v(users.c_vector().v); + const auto &v(users.c_vector().v); if (v.isEmpty() || (v.size() == 1 && v.at(0).type() == mtpc_inputUserSelf)) { _filter.setFocus(); _filter.showError(); @@ -1712,7 +1665,7 @@ void ContactsBox::removeAdminDone(UserData *user, const MTPBool &result) { } bool ContactsBox::saveAdminsFail(const RPCError &error) { - if (mtpIsFlood(error)) return true; + if (MTP::isDefaultHandledError(error)) return true; _saveRequestId = 0; _inner.saving(false); if (error.type() == qstr("CHAT_NOT_MODIFIED")) { @@ -1722,7 +1675,7 @@ bool ContactsBox::saveAdminsFail(const RPCError &error) { } bool ContactsBox::editAdminFail(const RPCError &error) { - if (mtpIsFlood(error)) return true; + if (MTP::isDefaultHandledError(error)) return true; --_saveRequestId; _inner.chat()->invalidateParticipants(); if (!_saveRequestId) { @@ -1765,7 +1718,7 @@ void ContactsBox::creationDone(const MTPUpdates &updates) { } bool ContactsBox::creationFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _saveRequestId = 0; if (error.type() == "NO_CHAT_TITLE") { @@ -1776,7 +1729,7 @@ 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))); @@ -1807,7 +1760,7 @@ MembersInner::MembersInner(ChannelData *channel, MembersFilter filter) : TWidget , _about(_aboutWidth) , _aboutHeight(0) { connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); - connect(App::main(), SIGNAL(peerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&))); + connect(App::main(), SIGNAL(peerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&))); connect(App::main(), SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(peerUpdated(PeerData*))); refresh(); @@ -2157,8 +2110,8 @@ void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result _loadingRequestId = 0; if (result.type() == mtpc_channels_channelParticipants) { - const MTPDchannels_channelParticipants &d(result.c_channels_channelParticipants()); - const QVector &v(d.vparticipants.c_vector().v); + const auto &d(result.c_channels_channelParticipants()); + const auto &v(d.vparticipants.c_vector().v); _rows.reserve(v.size()); _datas.reserve(v.size()); _dates.reserve(v.size()); @@ -2238,7 +2191,8 @@ void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result } bool MembersInner::membersFailed(const RPCError &error, mtpRequestId req) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; + Ui::hideLayer(); return true; } @@ -2259,7 +2213,7 @@ void MembersInner::kickAdminDone(const MTPUpdates &result, mtpRequestId req) { } bool MembersInner::kickFail(const RPCError &error, mtpRequestId req) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; if (_kickBox) _kickBox->onClose(); load(); diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h index 6f9eed12d..e1bb53673 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.h +++ b/Telegram/SourceFiles/boxes/contactsbox.h @@ -22,12 +22,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "abstractbox.h" +namespace Dialogs { +class Row; +class IndexedList; +} // namespace Dialogs + enum MembersFilter { MembersFilterRecent, MembersFilterAdmins, }; typedef QMap MembersAlreadyIn; +QString cantInviteError(); + class ConfirmBox; class ContactsInner : public TWidget, public RPCSender { Q_OBJECT @@ -51,7 +58,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()); @@ -67,7 +74,7 @@ public: void loadProfilePhotos(int32 yFrom); void chooseParticipant(); - void changeCheckState(DialogRow *row); + void changeCheckState(Dialogs::Row *row); void changeCheckState(ContactData *data, PeerData *peer); void peopleReceived(const QString &query, const QVector &people); @@ -100,7 +107,7 @@ signals: public slots: - void onDialogRowReplaced(DialogRow *oldRow, DialogRow *newRow); + void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow); void updateSel(); void peerUpdated(PeerData *peer); @@ -118,36 +125,38 @@ private: void addAdminDone(const MTPUpdates &result, mtpRequestId req); bool addAdminFail(const RPCError &error, mtpRequestId req); - int32 _rowHeight, _newItemHeight; - bool _newItemSel; + int32 _rowHeight; + int _newItemHeight = 0; + bool _newItemSel = false; - ChatData *_chat; - ChannelData *_channel; - MembersFilter _membersFilter; - UserData *_bot; - CreatingGroupType _creating; + ChatData *_chat = nullptr; + ChannelData *_channel = nullptr; + MembersFilter _membersFilter = MembersFilterRecent; + UserData *_bot = nullptr; + CreatingGroupType _creating = CreatingGroupNone; MembersAlreadyIn _already; Checkbox _allAdmins; int32 _aboutWidth; Text _aboutAllAdmins, _aboutAdmins; - PeerData *_addToPeer; - UserData *_addAdmin; - mtpRequestId _addAdminRequestId; - ConfirmBox *_addAdminBox; - + PeerData *_addToPeer = nullptr; + UserData *_addAdmin = nullptr; + mtpRequestId _addAdminRequestId = 0; + ConfirmBox *_addAdminBox = nullptr; + int32 _time; - DialogsIndexed *_contacts; - DialogRow *_sel; + std_::unique_ptr _customList; + Dialogs::IndexedList *_contacts = nullptr; + Dialogs::Row *_sel = nullptr; QString _filter; - typedef QVector FilteredDialogs; + typedef QVector FilteredDialogs; FilteredDialogs _filtered; - int32 _filteredSel; - bool _mouseSel; + int _filteredSel = -1; + bool _mouseSel = false; - int32 _selCount; + int _selCount = 0; struct ContactData { Text name; @@ -161,22 +170,22 @@ private: typedef QMap CheckedContacts; CheckedContacts _checkedContacts; - ContactData *contactData(DialogRow *row); + ContactData *contactData(Dialogs::Row *row); - bool _searching; + bool _searching = false; QString _lastQuery; typedef QVector ByUsernameRows; typedef QVector ByUsernameDatas; ByUsernameRows _byUsername, _byUsernameFiltered; ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas ByUsernameDatas _byUsernameDatas; - int32 _byUsernameSel; + int _byUsernameSel = -1; QPoint _lastMousePos; LinkButton _addContactLnk; - bool _saving; - bool _allAdminsChecked; + bool _saving = false; + bool _allAdminsChecked = false; }; diff --git a/Telegram/SourceFiles/boxes/downloadpathbox.cpp b/Telegram/SourceFiles/boxes/downloadpathbox.cpp index 00c7a7158..3da42b769 100644 --- a/Telegram/SourceFiles/boxes/downloadpathbox.cpp +++ b/Telegram/SourceFiles/boxes/downloadpathbox.cpp @@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localstorage.h" #include "downloadpathbox.h" -#include "gui/filedialog.h" +#include "ui/filedialog.h" #include "pspecific.h" DownloadPathBox::DownloadPathBox() : AbstractBox() diff --git a/Telegram/SourceFiles/boxes/emojibox.cpp b/Telegram/SourceFiles/boxes/emojibox.cpp index 9bd637bed..9aca04bc0 100644 --- a/Telegram/SourceFiles/boxes/emojibox.cpp +++ b/Telegram/SourceFiles/boxes/emojibox.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "emojibox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" namespace { // copied from genemoji.cpp diff --git a/Telegram/SourceFiles/boxes/languagebox.cpp b/Telegram/SourceFiles/boxes/languagebox.cpp index f6e7daf2f..8c7fe47ed 100644 --- a/Telegram/SourceFiles/boxes/languagebox.cpp +++ b/Telegram/SourceFiles/boxes/languagebox.cpp @@ -26,7 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "languagebox.h" #include "confirmbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" #include "langloaderplain.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, 253) + qsl("..."); - Ui::showLayer(new InformBox(qsl("Lang \"") + LanguageCodes[i] + qsl("\" warnings :(\n\nWarnings: ") + warn)); + 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/passcodebox.cpp b/Telegram/SourceFiles/boxes/passcodebox.cpp index c90e4c0b4..8468e86ff 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.cpp +++ b/Telegram/SourceFiles/boxes/passcodebox.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "passcodebox.h" #include "confirmbox.h" -#include "window.h" +#include "mainwindow.h" #include "localstorage.h" @@ -284,35 +284,12 @@ void PasscodeBox::setPasswordDone(const MTPBool &result) { } bool PasscodeBox::setPasswordFail(const RPCError &error) { - if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); - _setRequest = 0; - QString err = error.type(); - if (err == "PASSWORD_HASH_INVALID") { - if (_oldPasscode.isHidden()) { - emit reloadPassword(); - onClose(); - } else { - onBadOldPasscode(); - } - } else if (err == "NEW_PASSWORD_BAD") { - _newPasscode.setFocus(); - _newPasscode.showError(); - _newError = lang(lng_cloud_password_bad); - update(); - } else if (err == "NEW_SALT_INVALID") { - emit reloadPassword(); - onClose(); - } else if (err == "EMAIL_INVALID") { - _emailError = lang(lng_cloud_password_bad_email); - _recoverEmail.setFocus(); - _recoverEmail.showError(); - update(); - } else if (err == "EMAIL_UNCONFIRMED") { - Ui::showLayer(new InformBox(lang(lng_cloud_password_almost))); - emit reloadPassword(); - } else if (mtpIsFlood(error)) { + if (MTP::isFloodError(error)) { if (_oldPasscode.isHidden()) return false; + if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); + _setRequest = 0; + _oldPasscode.selectAll(); _oldPasscode.setFocus(); _oldPasscode.showError(); @@ -321,6 +298,36 @@ bool PasscodeBox::setPasswordFail(const RPCError &error) { _recover.hide(); } update(); + return true; + } + if (MTP::isDefaultHandledError(error)) return false; + + if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); + _setRequest = 0; + QString err = error.type(); + if (err == qstr("PASSWORD_HASH_INVALID")) { + if (_oldPasscode.isHidden()) { + emit reloadPassword(); + onClose(); + } else { + onBadOldPasscode(); + } + } else if (err == qstr("NEW_PASSWORD_BAD")) { + _newPasscode.setFocus(); + _newPasscode.showError(); + _newError = lang(lng_cloud_password_bad); + update(); + } else if (err == qstr("NEW_SALT_INVALID")) { + emit reloadPassword(); + onClose(); + } else if (err == qstr("EMAIL_INVALID")) { + _emailError = lang(lng_cloud_password_bad_email); + _recoverEmail.setFocus(); + _recoverEmail.showError(); + update(); + } else if (err == qstr("EMAIL_UNCONFIRMED")) { + Ui::showLayer(new InformBox(lang(lng_cloud_password_almost))); + emit reloadPassword(); } return true; } @@ -404,8 +411,8 @@ void PasscodeBox::onSave(bool force) { if (_oldPasscode.isHidden() || _newPasscode.isHidden()) { flags |= MTPDaccount_passwordInputSettings::Flag::f_email; } - MTPaccount_PasswordInputSettings settings(MTP_account_passwordInputSettings(MTP_flags(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); @@ -490,7 +497,7 @@ void PasscodeBox::recoverStarted(const MTPauth_PasswordRecovery &result) { } bool PasscodeBox::recoverStartFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _pattern = QString(); onClose(); @@ -587,32 +594,36 @@ void RecoverBox::codeSubmitDone(bool recover, const MTPauth_Authorization &resul } bool RecoverBox::codeSubmitFail(const RPCError &error) { + if (MTP::isFloodError(error)) { + _submitRequest = 0; + _error = lang(lng_flood_error); + update(); + _recoverCode.showError(); + return true; + } + if (MTP::isDefaultHandledError(error)) return false; + _submitRequest = 0; const QString &err = error.type(); - if (err == "PASSWORD_EMPTY") { + if (err == qstr("PASSWORD_EMPTY")) { emit reloadPassword(); Ui::showLayer(new InformBox(lang(lng_cloud_password_removed))); return true; - } else if (err == "PASSWORD_RECOVERY_NA") { + } else if (err == qstr("PASSWORD_RECOVERY_NA")) { onClose(); return true; - } else if (err == "PASSWORD_RECOVERY_EXPIRED") { + } else if (err == qstr("PASSWORD_RECOVERY_EXPIRED")) { emit recoveryExpired(); onClose(); return true; - } else if (err == "CODE_INVALID") { + } else if (err == qstr("CODE_INVALID")) { _error = lang(lng_signin_wrong_code); update(); _recoverCode.selectAll(); _recoverCode.setFocus(); _recoverCode.showError(); return true; - } else if (mtpIsFlood(error)) { - _error = lang(lng_flood_error); - update(); - _recoverCode.showError(); - return true; } if (cDebug()) { // internal server error _error = err + ": " + error.description(); diff --git a/Telegram/SourceFiles/boxes/photocropbox.cpp b/Telegram/SourceFiles/boxes/photocropbox.cpp index d7de86d98..0a2d477a3 100644 --- a/Telegram/SourceFiles/boxes/photocropbox.cpp +++ b/Telegram/SourceFiles/boxes/photocropbox.cpp @@ -19,7 +19,7 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" +#include "ui/style.h" #include "lang.h" #include "application.h" diff --git a/Telegram/SourceFiles/boxes/photosendbox.cpp b/Telegram/SourceFiles/boxes/photosendbox.cpp index f3a202ccc..7667d3471 100644 --- a/Telegram/SourceFiles/boxes/photosendbox.cpp +++ b/Telegram/SourceFiles/boxes/photosendbox.cpp @@ -19,7 +19,7 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" +#include "ui/style.h" #include "lang.h" #include "localstorage.h" @@ -51,8 +51,8 @@ PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxW if (_file->photo.type() != mtpc_photoEmpty) { _file->type = PreparePhoto; } else if (_file->document.type() == mtpc_document) { - const MTPDdocument &document(_file->document.c_document()); - const QVector &attributes(document.vattributes.c_vector().v); + const auto &document(_file->document.c_document()); + const auto &attributes(document.vattributes.c_vector().v); for (int32 i = 0, l = attributes.size(); i < l; ++i) { if (attributes.at(i).type() == mtpc_documentAttributeAnimated) { _animated = true; @@ -648,15 +648,15 @@ void EditCaptionBox::onSave(bool ctrlShiftEnter) { return; } - MTPchannels_EditMessage::Flags flags = 0; + MTPmessages_EditMessage::Flags flags = MTPmessages_EditMessage::Flag::f_message; if (_previewCancelled) { - flags |= MTPchannels_EditMessage::Flag::f_no_webpage; + flags |= MTPmessages_EditMessage::Flag::f_no_webpage; } MTPVector sentEntities; if (!sentEntities.c_vector().v.isEmpty()) { - flags |= MTPchannels_EditMessage::Flag::f_entities; + flags |= MTPmessages_EditMessage::Flag::f_entities; } - _saveRequestId = MTP::send(MTPchannels_EditMessage(MTP_flags(flags), item->history()->peer->asChannel()->inputChannel, MTP_int(item->id), MTP_string(_field->getLastText()), sentEntities), rpcDone(&EditCaptionBox::saveDone), rpcFail(&EditCaptionBox::saveFail)); + _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) { @@ -668,7 +668,7 @@ void EditCaptionBox::saveDone(const MTPUpdates &updates) { } bool EditCaptionBox::saveFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _saveRequestId = 0; QString err = error.type(); diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp index be56624fa..06d84ced1 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.cpp +++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp @@ -25,7 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "sessionsbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" #include "countries.h" #include "confirmbox.h" @@ -166,7 +166,7 @@ void SessionsInner::terminateDone(uint64 hash, const MTPBool &result) { } bool SessionsInner::terminateFail(uint64 hash, const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; TerminateButtons::iterator i = _terminateButtons.find(hash); if (i != _terminateButtons.end()) { @@ -181,7 +181,7 @@ void SessionsInner::terminateAllDone(const MTPBool &result) { } bool SessionsInner::terminateAllFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; emit allTerminated(); return true; } @@ -294,14 +294,14 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { int32 availOther = availCurrent - st::sessionTerminate.iconPos.x();// -st::sessionTerminate.width - st::sessionTerminateSkip; _list.clear(); - const QVector &v(result.c_account_authorizations().vauthorizations.c_vector().v); + const auto &v(result.c_account_authorizations().vauthorizations.c_vector().v); int32 l = v.size(); if (l > 1) _list.reserve(l - 1); const CountriesByISO2 &countries(countriesByISO2()); for (int32 i = 0; i < l; ++i) { - const MTPDauthorization &d(v.at(i).c_authorization()); + const auto &d(v.at(i).c_authorization()); SessionData data; data.hash = d.vhash.v; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index fd0d70e0a..814958521 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -23,10 +23,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stickersetbox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" #include "settingswidget.h" #include "boxes/confirmbox.h" - +#include "apiwrap.h" #include "localstorage.h" StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : TWidget() @@ -56,8 +56,8 @@ 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); + const auto &d(set.c_messages_stickerSet()); + const auto &v(d.vdocuments.c_vector().v); _pack.reserve(v.size()); for (int32 i = 0, l = v.size(); i < l; ++i) { DocumentData *doc = App::feedDocument(v.at(i)); @@ -65,12 +65,12 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { _pack.push_back(doc); } - const QVector &packs(d.vpacks.c_vector().v); + const auto &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()); + const auto &pack(packs.at(i).c_stickerPack()); if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) { - const QVector &stickers(pack.vdocuments.c_vector().v); + const auto &stickers(pack.vdocuments.c_vector().v); StickerPack p; p.reserve(stickers.size()); for (int32 j = 0, c = stickers.size(); j < c; ++j) { @@ -83,7 +83,7 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { } } if (d.vset.type() == mtpc_stickerSet) { - const MTPDstickerSet &s(d.vset.c_stickerSet()); + const auto &s(d.vset.c_stickerSet()); _setTitle = stickerSetTitle(s); _title = st::boxTitleFont->elided(_setTitle, width() - st::boxTitlePosition.x() - st::boxTitleHeight); _setShortName = qs(s.vshort_name); @@ -107,7 +107,7 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { } bool StickerSetInner::failedSet(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _loaded = true; @@ -152,7 +152,7 @@ void StickerSetInner::installDone(const MTPBool &result) { } bool StickerSetInner::installFailed(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; Ui::showLayer(new InformBox(lang(lng_stickers_not_found))); @@ -171,7 +171,7 @@ void StickerSetInner::mouseMoveEvent(QMouseEvent *e) { int32 index = stickerFromGlobalPos(e->globalPos()); if (index >= 0 && index < _pack.size() && index != _previewShown) { _previewShown = index; - Ui::showStickerPreview(_pack.at(_previewShown)); + Ui::showMediaPreview(_pack.at(_previewShown)); } } } @@ -184,7 +184,7 @@ void StickerSetInner::onPreview() { int32 index = stickerFromGlobalPos(QCursor::pos()); if (index >= 0 && index < _pack.size()) { _previewShown = index; - Ui::showStickerPreview(_pack.at(_previewShown)); + Ui::showMediaPreview(_pack.at(_previewShown)); } } @@ -803,7 +803,7 @@ void StickersBox::disenableDone(const MTPBool & result, mtpRequestId req) { } bool StickersBox::disenableFail(const RPCError &error, mtpRequestId req) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _disenableRequests.remove(req); if (_disenableRequests.isEmpty()) { saveOrder(); @@ -831,7 +831,7 @@ void StickersBox::reorderDone(const MTPBool &result) { } bool StickersBox::reorderFail(const RPCError &result) { - if (mtpIsFlood(result)) return false; + if (MTP::isDefaultHandledError(result)) return false; _reorderRequest = 0; Global::SetLastStickersUpdate(0); App::main()->updateStickers(); diff --git a/Telegram/SourceFiles/boxes/usernamebox.cpp b/Telegram/SourceFiles/boxes/usernamebox.cpp index 247db630f..7d7bb157f 100644 --- a/Telegram/SourceFiles/boxes/usernamebox.cpp +++ b/Telegram/SourceFiles/boxes/usernamebox.cpp @@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "application.h" #include "usernamebox.h" #include "mainwidget.h" -#include "window.h" +#include "mainwindow.h" UsernameBox::UsernameBox() : AbstractBox(st::boxWidth), _save(this, lang(lng_settings_save), st::defaultBoxButton), @@ -202,22 +202,22 @@ void UsernameBox::onUpdateDone(const MTPUser &user) { } bool UsernameBox::onUpdateFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _saveRequestId = 0; QString err(error.type()); - if (err == "USERNAME_NOT_MODIFIED" || _sentUsername == App::self()->username) { + if (err == qstr("USERNAME_NOT_MODIFIED") || _sentUsername == App::self()->username) { App::self()->setName(textOneLine(App::self()->firstName), textOneLine(App::self()->lastName), textOneLine(App::self()->nameOrPhone), textOneLine(_sentUsername)); emit closed(); return true; - } else if (err == "USERNAME_INVALID") { + } else if (err == qstr("USERNAME_INVALID")) { _username.setFocus(); _username.showError(); _copiedTextLink = QString(); _errorText = lang(lng_username_invalid); update(); return true; - } else if (err == "USERNAME_OCCUPIED" || err == "USERNAMES_UNAVAILABLE") { + } else if (err == qstr("USERNAME_OCCUPIED") || err == qstr("USERNAMES_UNAVAILABLE")) { _username.setFocus(); _username.showError(); _copiedTextLink = QString(); @@ -242,15 +242,15 @@ void UsernameBox::onCheckDone(const MTPBool &result) { } bool UsernameBox::onCheckFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; _checkRequestId = 0; QString err(error.type()); - if (err == "USERNAME_INVALID") { + if (err == qstr("USERNAME_INVALID")) { _errorText = lang(lng_username_invalid); update(); return true; - } else if (err == "USERNAME_OCCUPIED" && _checkUsername != App::self()->username) { + } else if (err == qstr("USERNAME_OCCUPIED") && _checkUsername != App::self()->username) { _errorText = lang(lng_username_occupied); update(); return true; diff --git a/Telegram/SourceFiles/codegen/style/main.cpp b/Telegram/SourceFiles/codegen/style/main.cpp new file mode 100644 index 000000000..a553b5173 --- /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 39e5008f3..948dd8380 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -20,10 +20,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -static const int32 AppVersion = 9036; -static const wchar_t *AppVersionStr = L"0.9.36"; -static const bool DevVersion = true; -//#define BETA_VERSION (9034004ULL) // just comment this line to build public version +static const int32 AppVersion = 9042; +static const wchar_t *AppVersionStr = L"0.9.42"; +static const bool DevVersion = false; +//#define BETA_VERSION (9040128ULL) // 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"; @@ -139,7 +139,6 @@ enum { EmojiPanRowsPerPage = 6, StickerPanPerRow = 5, StickerPanRowsPerPage = 4, - SavedGifsMaxPerRow = 4, StickersUpdateTimeout = 3600000, // update not more than once in an hour SearchPeopleLimit = 5, @@ -296,7 +295,7 @@ static const char *ApiHash = "344583e45741c457fe1862106095a5eb"; #else static const char *BetaPrivateKey = ""; #undef BETA_VERSION -#define BETA_VERSION 0 +#define BETA_VERSION (0) #endif inline const char *cApiDeviceModel() { diff --git a/Telegram/SourceFiles/types.cpp b/Telegram/SourceFiles/core/basic_types.cpp similarity index 99% rename from Telegram/SourceFiles/types.cpp rename to Telegram/SourceFiles/core/basic_types.cpp index e4541be90..5d1bc6874 100644 --- a/Telegram/SourceFiles/types.cpp +++ b/Telegram/SourceFiles/core/basic_types.cpp @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "types.h" +#include "basic_types.h" #include #include diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/core/basic_types.h similarity index 79% rename from Telegram/SourceFiles/types.h rename to Telegram/SourceFiles/core/basic_types.h index 836b7af37..75757ba02 100644 --- a/Telegram/SourceFiles/types.h +++ b/Telegram/SourceFiles/core/basic_types.h @@ -169,12 +169,8 @@ template char(&ArraySizeHelper(T(&array)[N]))[N]; // 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 -template -struct ForConstTraits { - typedef const T &ExpressionType; -}; -#define for_const(range_declaration, range_expression) for (range_declaration : static_cast::ExpressionType>(range_expression)) +// 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) { @@ -198,6 +194,31 @@ T *SharedMemoryLocation() { 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; @@ -215,40 +236,187 @@ typedef double float64; using std::string; using std::exception; -using std::swap; -// we copy some parts of C++11 std:: library, because on OS X 10.6+ -// version we can use C++11, but we can't use its library :( -namespace std11 { +// 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 { - typedef T type; + using type = T; }; template struct remove_reference { - typedef T type; + using type = T; }; template struct remove_reference { - typedef T type; + using type = T; }; template -inline typename remove_reference::type &&move(T &&value) { +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); } -} // namespace std11 +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; + +// This is not full unique_ptr, but at least with std interface. +template +class unique_ptr { +public: + constexpr unique_ptr() noexcept = default; + unique_ptr(const unique_ptr &) = delete; + unique_ptr &operator=(const unique_ptr &) = delete; + + constexpr unique_ptr(std::nullptr_t) { + } + unique_ptr &operator=(std::nullptr_t) noexcept { + reset(); + return (*this); + } + + explicit unique_ptr(T *p) noexcept : _p(p) { + } + + template + unique_ptr(unique_ptr &&other) noexcept : _p(other.release()) { + } + template + unique_ptr &operator=(unique_ptr &&other) noexcept { + reset(other.release()); + return (*this); + } + unique_ptr &operator=(unique_ptr &&other) noexcept { + if (this != &other) { + reset(other.release()); + } + return (*this); + } + + void swap(unique_ptr &other) noexcept { + std::swap(_p, other._p); + } + ~unique_ptr() noexcept { + delete _p; + } + + T &operator*() const { + return (*get()); + } + T *operator->() const noexcept { + return get(); + } + T *get() const noexcept { + return _p; + } + explicit operator bool() const noexcept { + return get() != nullptr; + } + + T *release() noexcept { + return getPointerAndReset(_p); + } + + void reset(T *p = nullptr) noexcept { + T *old = _p; + _p = p; + if (old) { + delete old; + } + } + +private: + T *_p = nullptr; + +}; + +template +inline unique_ptr make_unique(Args&&... args) { + return unique_ptr(new T(forward(args)...)); +} + +template +inline bool operator==(const unique_ptr &a, std::nullptr_t) noexcept { + return !a; +} +template +inline bool operator==(std::nullptr_t, const unique_ptr &b) noexcept { + return !b; +} +template +inline bool operator!=(const unique_ptr &a, std::nullptr_t b) noexcept { + return !(a == b); +} +template +inline bool operator!=(std::nullptr_t a, const unique_ptr &b) noexcept { + return !(a == b); +} + +} // namespace std_ #include "logs.h" -static volatile int *t_assert_nullptr = 0; +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::setAssertionInfo(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()) @@ -595,20 +763,22 @@ MimeType mimeTypeForName(const QString &mime); MimeType mimeTypeForFile(const QFileInfo &file); MimeType mimeTypeForData(const QByteArray &data); -inline int32 rowscount(int32 count, int32 perrow) { - return (count + perrow - 1) / perrow; +#include + +inline int rowscount(int fullCount, int countPerRow) { + return (fullCount + countPerRow - 1) / countPerRow; } -inline int32 floorclamp(int32 value, int32 step, int32 lowest, int32 highest) { +inline int floorclamp(int value, int step, int lowest, int highest) { return qMin(qMax(value / step, lowest), highest); } -inline int32 floorclamp(float64 value, int32 step, int32 lowest, int32 highest) { - return qMin(qMax(qFloor(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 int32 ceilclamp(int32 value, int32 step, int32 lowest, int32 highest) { - return qMax(qMin((value / step) + ((value % step) ? 1 : 0), highest), lowest); +inline int ceilclamp(int value, int step, int lowest, int highest) { + return qMax(qMin((value + step - 1) / step, highest), lowest); } -inline int32 ceilclamp(float64 value, int32 step, int32 lowest, int32 highest) { - return qMax(qMin(qCeil(value / 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 { @@ -633,41 +803,110 @@ static int32 QuarterArcLength = (FullArcLength / 4); static int32 MinArcLength = (FullArcLength / 360); static int32 AlmostFullArcLength = (FullArcLength - MinArcLength); -template -class RefPairImplementation { +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: - template - const RefPairImplementation &operator=(const RefPairImplementation &other) const { - _first = other._first; - _second = other._second; - return *this; + explicit NeverFreedPointer() { + } + NeverFreedPointer(const NeverFreedPointer &other) = delete; + NeverFreedPointer &operator=(const NeverFreedPointer &other) = delete; + + template + void createIfNull(U creator) { + if (isNull()) { + reset(creator()); + } } - template - const RefPairImplementation &operator=(const QPair &other) const { - _first = other.first; - _second = other.second; - return *this; + 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: - RefPairImplementation(T1 &first, T2 &second) : _first(first), _second(second) { - } - RefPairImplementation(const RefPairImplementation &other); + T *_p = nullptr; - template - friend RefPairImplementation RefPairCreator(T3 &first, T4 &second); - - T1 &_first; - T2 &_second; }; -template -inline RefPairImplementation RefPairCreator(T1 &first, T2 &second) { - return RefPairImplementation(first, second); -} +// 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; -#define RefPair(Type1, Name1, Type2, Name2) Type1 Name1; Type2 Name2; RefPairCreator(Name1, Name2) + 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) { @@ -705,26 +944,11 @@ struct CeilDivideMinimumOne { static const int Result = ((Value / Denominator) + ((!Value || (Value % Denominator)) ? 1 : 0)); }; -template -struct ComponentWrapTemplate { - static const int Size = CeilDivideMinimumOne::Result * sizeof(uint64); - static void Construct(void *location, Composer *composer) { - new (location) Type(composer); - } - static void Destruct(void *location) { - ((Type*)location)->~Type(); - } - static void Move(void *location, void *waslocation) { - *(Type*)location = std11::move(*(Type*)waslocation); - } -}; - extern ComponentWrapStruct ComponentWraps[64]; extern QAtomicInt ComponentIndexLast; template -class BaseComponent { -public: +struct BaseComponent { BaseComponent() { } BaseComponent(const BaseComponent &other) = delete; @@ -742,7 +966,9 @@ public: if (ComponentIndexLast.testAndSetOrdered(last, last + 1)) { t_assert(last < 64); if (_index.testAndSetOrdered(0, last + 1)) { - ComponentWraps[last] = ComponentWrapStruct(ComponentWrapTemplate::Size, ComponentWrapTemplate::Construct, ComponentWrapTemplate::Destruct, ComponentWrapTemplate::Move); + ComponentWraps[last] = ComponentWrapStruct( + CeilDivideMinimumOne::Result * sizeof(uint64), + Type::ComponentConstruct, Type::ComponentDestruct, Type::ComponentMove); } break; } @@ -753,6 +979,17 @@ public: 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 { @@ -848,6 +1085,21 @@ public: } } + 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); @@ -870,19 +1122,6 @@ public: UpdateComponents(_meta()->maskremove(mask)); } - template - Type *Get() { - return static_cast(_dataptr(_meta()->offsets[Type::Index()])); - } - template - const Type *Get() const { - return static_cast(_dataptr(_meta()->offsets[Type::Index()])); - } - template - bool Has() const { - return (_meta()->offsets[Type::Index()] >= 0); - } - private: static const ComposerMetadata *ZeroComposerMetadata; static void *zerodata() { @@ -906,13 +1145,13 @@ private: }; -template -class SharedCallback2 { +template +class SharedCallback { public: - virtual R call(A1 channel, A2 msgId) const = 0; - virtual ~SharedCallback2() { + virtual R call(Args... args) const = 0; + virtual ~SharedCallback() { } - typedef QSharedPointer > Ptr; + typedef QSharedPointer> Ptr; }; template diff --git a/Telegram/SourceFiles/core/click_handler.cpp b/Telegram/SourceFiles/core/click_handler.cpp new file mode 100644 index 000000000..09809de14 --- /dev/null +++ b/Telegram/SourceFiles/core/click_handler.cpp @@ -0,0 +1,63 @@ +/* +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 +*/ +#include "stdafx.h" +#include "core/click_handler.h" + +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; +} diff --git a/Telegram/SourceFiles/core/click_handler.h b/Telegram/SourceFiles/core/click_handler.h new file mode 100644 index 000000000..8c997e6b9 --- /dev/null +++ b/Telegram/SourceFiles/core/click_handler.h @@ -0,0 +1,158 @@ +/* +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 + +class ClickHandler; +using ClickHandlerPtr = QSharedPointer; + +class ClickHandlerHost { +protected: + + virtual void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { + } + virtual void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) { + } + virtual ~ClickHandlerHost() = 0; + friend class ClickHandler; + +}; + +class ClickHandler { +public: + + 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(); + } + + virtual ~ClickHandler() { + } + + // 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); + + // 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); + } + + // 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; + } + 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; + +}; diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp new file mode 100644 index 000000000..3c317c3dd --- /dev/null +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -0,0 +1,133 @@ +/* +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 +*/ +#include "stdafx.h" +#include "core/click_handler_types.h" + +#include "lang.h" +#include "pspecific.h" +#include "boxes/confirmbox.h" + +QString UrlClickHandler::copyToClipboardContextItem() const { + return lang(isEmail() ? lng_context_copy_email : lng_context_copy_link); +} + +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 { + App::insertBotCommand(_cmd); + } + } +} diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h new file mode 100644 index 000000000..9ca66ad85 --- /dev/null +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -0,0 +1,181 @@ +/* +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 + +#include "core/click_handler.h" + +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; + } + +protected: + virtual QString url() const = 0; + virtual QString readable() const { + return url(); + } + + bool _fullDisplayed; + +}; + +class UrlClickHandler : public TextClickHandler { +public: + 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; + } + } + 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 MentionClickHandler : public TextClickHandler { +public: + MentionClickHandler(const QString &tag) : _tag(tag) { + } + QString copyToClipboardContextItem() const override; + + QString text() const override { + return _tag; + } + void onClick(Qt::MouseButton button) const override; + +protected: + QString url() const override { + return _tag; + } + +private: + QString _tag; + +}; + +class HashtagClickHandler : public TextClickHandler { +public: + HashtagClickHandler(const QString &tag) : _tag(tag) { + } + QString copyToClipboardContextItem() const override; + + QString text() const override { + return _tag; + } + void onClick(Qt::MouseButton button) const override; + +protected: + QString url() const override { + return _tag; + } + +private: + QString _tag; + +}; + +class BotCommandClickHandler : public TextClickHandler { +public: + BotCommandClickHandler(const QString &cmd) : _cmd(cmd) { + } + QString text() const override { + return _cmd; + } + void onClick(Qt::MouseButton button) const override; + +protected: + QString url() const override { + return _cmd; + } + +private: + QString _cmd; + +}; diff --git a/Telegram/SourceFiles/dialogs/dialogs_common.h b/Telegram/SourceFiles/dialogs/dialogs_common.h new file mode 100644 index 000000000..dd45fea13 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_common.h @@ -0,0 +1,39 @@ +/* +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 + +namespace Dialogs { + +class Row; +using RowsByLetter = QMap; + +enum class SortMode { + Date = 0x00, + Name = 0x01, + Add = 0x02, +}; + +enum class Mode { + All = 0x00, + Important = 0x01, +}; + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp new file mode 100644 index 000000000..7c262f3aa --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp @@ -0,0 +1,177 @@ +/* +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 +*/ +#include "stdafx.h" +#include "dialogs/dialogs_indexed_list.h" + +namespace Dialogs { + +IndexedList::IndexedList(SortMode sortMode) +: _sortMode(sortMode) +, _list(sortMode) { +} + +RowsByLetter IndexedList::addToEnd(History *history) { + RowsByLetter result; + if (!_list.contains(history->peer->id)) { + result.insert(0, _list.addToEnd(history)); + for_const (auto ch, history->peer->chars) { + auto j = _index.find(ch); + if (j == _index.cend()) { + j = _index.insert(ch, new List(_sortMode)); + } + result.insert(ch, j.value()->addToEnd(history)); + } + } + return result; +} + +Row *IndexedList::addByName(History *history) { + if (auto row = _list.getRow(history->peer->id)) { + return row; + } + + Row *result = _list.addByName(history); + for_const (auto ch, history->peer->chars) { + auto j = _index.find(ch); + if (j == _index.cend()) { + j = _index.insert(ch, new List(_sortMode)); + } + j.value()->addByName(history); + } + return result; +} + +void IndexedList::adjustByPos(const RowsByLetter &links) { + for (auto i = links.cbegin(), e = links.cend(); i != e; ++i) { + if (i.key() == QChar(0)) { + _list.adjustByPos(i.value()); + } else { + if (auto list = _index.value(i.key())) { + list->adjustByPos(i.value()); + } + } + } +} + +void IndexedList::peerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { + t_assert(_sortMode != SortMode::Date); + if (_sortMode == SortMode::Name) { + adjustByName(peer, oldNames, oldChars); + } else { + adjustNames(Dialogs::Mode::All, peer, oldNames, oldChars); + } +} + +void IndexedList::peerNameChanged(Mode list, PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { + t_assert(_sortMode == SortMode::Date); + adjustNames(list, peer, oldNames, oldChars); +} + +void IndexedList::adjustByName(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { + Row *mainRow = _list.adjustByName(peer); + if (!mainRow) return; + + History *history = mainRow->history(); + + PeerData::NameFirstChars toRemove = oldChars, toAdd; + for_const (auto ch, peer->chars) { + auto j = toRemove.find(ch); + if (j == toRemove.cend()) { + toAdd.insert(ch); + } else { + toRemove.erase(j); + if (auto list = _index.value(ch)) { + list->adjustByName(peer); + } + } + } + for_const (auto ch, toRemove) { + if (auto list = _index.value(ch)) { + list->del(peer->id, mainRow); + } + } + if (!toAdd.isEmpty()) { + for_const (auto ch, toAdd) { + auto j = _index.find(ch); + if (j == _index.cend()) { + j = _index.insert(ch, new List(_sortMode)); + } + j.value()->addByName(history); + } + } +} + +void IndexedList::adjustNames(Mode list, PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { + auto mainRow = _list.getRow(peer->id); + if (!mainRow) return; + + History *history = mainRow->history(); + + PeerData::NameFirstChars toRemove = oldChars, toAdd; + for_const (auto ch, peer->chars) { + auto j = toRemove.find(ch); + if (j == toRemove.cend()) { + toAdd.insert(ch); + } else { + toRemove.erase(j); + } + } + for_const (auto ch, toRemove) { + if (_sortMode == SortMode::Date) { + history->removeChatListEntryByLetter(list, ch); + } + if (auto list = _index.value(ch)) { + list->del(peer->id, mainRow); + } + } + for_const (auto ch, toAdd) { + auto j = _index.find(ch); + if (j == _index.cend()) { + j = _index.insert(ch, new List(_sortMode)); + } + Row *row = j.value()->addToEnd(history); + if (_sortMode == SortMode::Date) { + history->addChatListEntryByLetter(list, ch, row); + } + } +} + +void IndexedList::del(const PeerData *peer, Row *replacedBy) { + if (_list.del(peer->id, replacedBy)) { + for_const (auto ch, peer->chars) { + if (auto list = _index.value(ch)) { + list->del(peer->id, replacedBy); + } + } + } +} + +void IndexedList::clear() { + for_const (auto &list, _index) { + delete list; + } +} + +IndexedList::~IndexedList() { + clear(); +} + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h new file mode 100644 index 000000000..6a5967977 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h @@ -0,0 +1,90 @@ +/* +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 + +#include "dialogs/dialogs_common.h" +#include "dialogs/dialogs_list.h" + +class History; + +namespace Dialogs { + +class IndexedList { +public: + IndexedList(SortMode sortMode); + + RowsByLetter addToEnd(History *history); + Row *addByName(History *history); + void adjustByPos(const RowsByLetter &links); + + // For sortMode != SortMode::Date + void peerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); + + //For sortMode == SortMode::Date + void peerNameChanged(Mode list, PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); + + void del(const PeerData *peer, Row *replacedBy = nullptr); + void clear(); + + const List &all() const { + return _list; + } + const List *filtered(QChar ch) const { + static StaticNeverFreedPointer empty(new List(SortMode::Add)); + return _index.value(ch, empty.data()); + } + + ~IndexedList(); + + // Part of List interface is duplicated here for all() list. + int size() const { return all().size(); } + bool isEmpty() const { return all().isEmpty(); } + bool contains(PeerId peerId) const { return all().contains(peerId); } + Row *getRow(PeerId peerId) const { return all().getRow(peerId); } + Row *rowAtY(int32 y, int32 h) const { return all().rowAtY(y, h); } + + using iterator = List::iterator; + using const_iterator = List::const_iterator; + const_iterator cbegin() const { return all().cbegin(); } + const_iterator cend() const { return all().cend(); } + const_iterator begin() const { return all().cbegin(); } + const_iterator end() const { return all().cend(); } + iterator begin() { return all().begin(); } + iterator end() { return all().end(); } + const_iterator cfind(Row *value) const { return all().cfind(value); } + const_iterator find(Row *value) const { return all().cfind(value); } + iterator find(Row *value) { return all().find(value); } + const_iterator cfind(int y, int h) const { return all().cfind(y, h); } + const_iterator find(int y, int h) const { return all().cfind(y, h); } + iterator find(int y, int h) { return all().find(y, h); } + +private: + void adjustByName(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); + void adjustNames(Mode list, PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); + + SortMode _sortMode; + List _list; + using Index = QMap; + Index _index; + +}; + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp new file mode 100644 index 000000000..64671379c --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -0,0 +1,276 @@ +/* +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 +*/ +#include "stdafx.h" +#include "dialogs/dialogs_layout.h" + +#include "dialogs/dialogs_list.h" +#include "lang.h" + +namespace Dialogs { +namespace Layout { + +namespace { + +template +void paintRow(Painter &p, History *history, HistoryItem *item, int w, bool active, bool selected, bool onlyBackground, PaintItemCallback paintItemCallback) { + QRect fullRect(0, 0, w, st::dlgHeight); + p.fillRect(fullRect, active ? st::dlgActiveBG : (selected ? st::dlgHoverBG : st::dlgBG)); + if (onlyBackground) return; + + 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; + QRect rectForName(nameleft, st::dlgPaddingVer + st::dlgNameTop, namewidth, st::msgNameFont->height); + + // draw chat icon + if (history->peer->isChat() || history->peer->isMegagroup()) { + p.drawSprite(QPoint(rectForName.left() + st::dlgChatImgPos.x(), rectForName.top() + st::dlgChatImgPos.y()), (active ? st::dlgActiveChatImg : st::dlgChatImg)); + rectForName.setLeft(rectForName.left() + st::dlgImgSkip); + } else if (history->peer->isChannel()) { + p.drawSprite(QPoint(rectForName.left() + st::dlgChannelImgPos.x(), rectForName.top() + st::dlgChannelImgPos.y()), (active ? st::dlgActiveChannelImg : st::dlgChannelImg)); + rectForName.setLeft(rectForName.left() + st::dlgImgSkip); + } + + if (!item) { + p.setFont(st::dlgHistFont); + p.setPen(active ? st::dlgActiveColor : st::dlgSystemColor); + if (history->typing.isEmpty() && history->sendActions.isEmpty()) { + p.drawText(nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgFont->ascent + st::dlgSep, lang(lng_empty_history)); + } else { + history->typingText.drawElided(p, nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgSep, namewidth); + } + } else { + // draw date + QDateTime now(QDateTime::currentDateTime()), lastTime(item->date); + QDate nowDate(now.date()), lastDate(lastTime.date()); + QString dt; + if (lastDate == nowDate) { + dt = lastTime.toString(cTimeFormat()); + } else if (lastDate.year() == nowDate.year() && lastDate.weekNumber() == nowDate.weekNumber()) { + dt = langDayOfWeek(lastDate); + } else { + dt = lastDate.toString(qsl("d.MM.yy")); + } + int32 dtWidth = st::dlgDateFont->width(dt); + rectForName.setWidth(rectForName.width() - dtWidth - st::dlgDateSkip); + p.setFont(st::dlgDateFont); + p.setPen(active ? st::dlgActiveDateColor : st::dlgDateColor); + p.drawText(rectForName.left() + rectForName.width() + st::dlgDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, dt); + + // draw check + if (item->needCheck()) { + const style::sprite *check; + if (item->id > 0) { + if (item->unread()) { + check = active ? &st::dlgActiveCheckImg : &st::dlgCheckImg; + } else { + check = active ? &st::dlgActiveDblCheckImg : &st::dlgDblCheckImg; + } + } else { + check = active ? &st::dlgActiveSendImg : &st::dlgSendImg; + } + rectForName.setWidth(rectForName.width() - check->pxWidth() - st::dlgCheckSkip); + p.drawSprite(QPoint(rectForName.left() + rectForName.width() + st::dlgCheckLeft, rectForName.top() + st::dlgCheckTop), *check); + } + + paintItemCallback(nameleft, namewidth, item); + } + + if (history->peer->isUser() && history->peer->isVerified()) { + rectForName.setWidth(rectForName.width() - st::verifiedCheck.pxWidth() - st::verifiedCheckPos.x()); + p.drawSprite(rectForName.topLeft() + QPoint(qMin(history->peer->dialogName().maxWidth(), rectForName.width()), 0) + st::verifiedCheckPos, (active ? st::verifiedCheckInv : st::verifiedCheck)); + } + + p.setPen(active ? st::dlgActiveColor : st::dlgNameColor); + history->peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); +} + +class UnreadBadgeStyle : public StyleSheet { +public: + QImage circle; + QPixmap left[4], right[4]; + style::color bg[4] = { st::dlgUnreadBG, st::dlgActiveUnreadBG, st::dlgUnreadMutedBG, st::dlgActiveUnreadMutedBG }; +}; +StyleSheetPointer unreadBadgeStyle; + +void createCircleMask(int size) { + if (!unreadBadgeStyle->circle.isNull()) return; + + unreadBadgeStyle->circle = QImage(size, size, QImage::Format::Format_Grayscale8); + { + QPainter pcircle(&unreadBadgeStyle->circle); + pcircle.setRenderHint(QPainter::HighQualityAntialiasing, true); + pcircle.fillRect(0, 0, size, size, QColor(0, 0, 0)); + pcircle.setPen(Qt::NoPen); + pcircle.setBrush(QColor(255, 255, 255)); + pcircle.drawEllipse(0, 0, size, size); + } + unreadBadgeStyle->circle.setDevicePixelRatio(cRetinaFactor()); +} + +QImage colorizeCircleHalf(int size, int half, int xoffset, style::color color) { + int a = color->c.alpha() + 1; + int fg_r = color->c.red() * a, fg_g = color->c.green() * a, fg_b = color->c.blue() * a, fg_a = 255 * a; + + QImage result(size, size, QImage::Format_ARGB32_Premultiplied); + uchar *bits = result.bits(), *maskbits = unreadBadgeStyle->circle.bits(); + int bpl = result.bytesPerLine(), maskbpl = unreadBadgeStyle->circle.bytesPerLine(); + for (int x = 0; x < size; ++x) { + for (int y = 0; y < size; ++y) { + int s = y * bpl + (x * 4); + int o = maskbits[y * maskbpl + x + xoffset] + 1; + bits[s + 0] = (fg_b * o) >> 16; + bits[s + 1] = (fg_g * o) >> 16; + bits[s + 2] = (fg_r * o) >> 16; + bits[s + 3] = (fg_a * o) >> 16; + } + } + result.setDevicePixelRatio(cRetinaFactor()); + return result; +} + +void paintUnreadBadge(Painter &p, const QRect &rect, bool active, bool muted) { + int index = (active ? 0x01 : 0x00) | (muted ? 0x02 : 0x00); + int size = rect.height(), sizehalf = size / 2; + + unreadBadgeStyle.createIfNull(); + style::color bg = unreadBadgeStyle->bg[index]; + if (unreadBadgeStyle->left[index].isNull()) { + int imgsize = size * cIntRetinaFactor(), imgsizehalf = sizehalf * cIntRetinaFactor(); + createCircleMask(imgsize); + unreadBadgeStyle->left[index] = QPixmap::fromImage(colorizeCircleHalf(imgsize, imgsizehalf, 0, bg)); + unreadBadgeStyle->right[index] = QPixmap::fromImage(colorizeCircleHalf(imgsize, imgsizehalf, imgsize - imgsizehalf, bg)); + } + + int bar = rect.width() - 2 * sizehalf; + p.drawPixmap(rect.x(), rect.y(), unreadBadgeStyle->left[index]); + if (bar) { + p.fillRect(rect.x() + sizehalf, rect.y(), bar, rect.height(), bg); + } + p.drawPixmap(rect.x() + sizehalf + bar, rect.y(), unreadBadgeStyle->right[index]); +} + +void paintUnreadCount(Painter &p, const QString &text, int top, int w, bool active, bool muted, int *outAvailableWidth) { + int unreadWidth = st::dlgUnreadFont->width(text); + int unreadRectWidth = unreadWidth + 2 * st::dlgUnreadPaddingHor; + int unreadRectHeight = st::dlgUnreadHeight; + accumulate_max(unreadRectWidth, unreadRectHeight); + + int unreadRectLeft = w - st::dlgPaddingHor - unreadRectWidth; + int unreadRectTop =top; + if (outAvailableWidth) { + *outAvailableWidth -= unreadRectWidth + st::dlgUnreadPaddingHor; + } + + paintUnreadBadge(p, QRect(unreadRectLeft, unreadRectTop, unreadRectWidth, unreadRectHeight), active, muted); + + p.setFont(st::dlgUnreadFont); + p.setPen(active ? st::dlgActiveUnreadColor : st::dlgUnreadColor); + p.drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + st::dlgUnreadTop + st::dlgUnreadFont->ascent, text); +} + +} // namepsace + +void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool selected, bool onlyBackground) { + auto history = row->history(); + auto item = history->lastMsg; + paintRow(p, history, item, w, active, selected, onlyBackground, [&p, w, active, history](int nameleft, int namewidth, HistoryItem *item) { + int32 unread = history->unreadCount(); + if (history->peer->migrateFrom()) { + if (History *h = App::historyLoaded(history->peer->migrateFrom()->id)) { + unread += h->unreadCount(); + } + } + int availableWidth = namewidth; + int texttop = st::dlgPaddingVer + st::dlgFont->height + st::dlgSep; + if (unread) { + int unreadTop = texttop + st::dlgHistFont->ascent - st::dlgUnreadFont->ascent - st::dlgUnreadTop; + paintUnreadCount(p, QString::number(unread), unreadTop, w, active, history->mute(), &availableWidth); + } + if (history->typing.isEmpty() && history->sendActions.isEmpty()) { + item->drawInDialog(p, QRect(nameleft, texttop, availableWidth, st::dlgFont->height), active, history->textCachedFor, history->lastItemTextCache); + } else { + p.setPen(active ? st::dlgActiveColor : st::dlgSystemColor); + history->typingText.drawElided(p, nameleft, texttop, availableWidth); + } + }); +} + +void RowPainter::paint(Painter &p, const FakeRow *row, int w, bool active, bool selected, bool onlyBackground) { + auto item = row->item(); + auto history = item->history(); + paintRow(p, history, item, w, active, selected, onlyBackground, [&p, row, active](int nameleft, int namewidth, HistoryItem *item) { + int lastWidth = namewidth, texttop = st::dlgPaddingVer + st::dlgFont->height + st::dlgSep; + item->drawInDialog(p, QRect(nameleft, texttop, lastWidth, st::dlgFont->height), active, row->_cacheFor, row->_cache); + }); +} + +void paintImportantSwitch(Painter &p, Mode current, int w, bool selected, bool onlyBackground) { + p.fillRect(0, 0, w, st::dlgImportantHeight, selected ? st::dlgHoverBG : st::white); + if (onlyBackground) { + return; + } + + p.setFont(st::semiboldFont); + p.setPen(st::black); + + int unreadTop = (st::dlgImportantHeight - st::dlgUnreadHeight) / 2; + bool mutedHidden = (current == Dialogs::Mode::Important); + QString text = mutedHidden ? qsl("Show all chats") : qsl("Hide muted chats"); + int textBaseline = unreadTop + st::dlgUnreadTop + st::dlgUnreadFont->ascent; + p.drawText(st::dlgPaddingHor, textBaseline, text); + + if (mutedHidden) { + if (int32 unread = App::histories().unreadMutedCount()) { + paintUnreadCount(p, QString::number(unread), unreadTop, w, false, true, nullptr); + } + } +} + +namespace { + +using StyleSheets = OrderedSet; +NeverFreedPointer styleSheets; + +} + +namespace internal { + +void registerStyleSheet(StyleSheet **p) { + styleSheets.makeIfNull(); + styleSheets->insert(p); +} + +} // namespace internal + +void clearStyleSheets() { + if (!styleSheets) return; + for (auto &p : *styleSheets) { + delete (*p); + *p = nullptr; + } + styleSheets.clear(); +} + +} // namespace Layout +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.h b/Telegram/SourceFiles/dialogs/dialogs_layout.h new file mode 100644 index 000000000..1c0de56a1 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.h @@ -0,0 +1,78 @@ +/* +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 + +namespace Dialogs { + +class Row; +class FakeRow; + +namespace Layout { + +class RowPainter { +public: + static void paint(Painter &p, const Row *row, int w, bool active, bool selected, bool onlyBackground); + static void paint(Painter &p, const FakeRow *row, int w, bool active, bool selected, bool onlyBackground); +}; + +void paintImportantSwitch(Painter &p, Mode current, int w, bool selected, bool onlyBackground); + +// This will be moved somewhere outside as soon as anyone starts using that. +class StyleSheet { +public: + virtual ~StyleSheet() = 0; +}; +inline StyleSheet::~StyleSheet() = default; + +namespace internal { + +void registerStyleSheet(StyleSheet **p); + +} // namespace + +// Must be created in global scope! +template +class StyleSheetPointer { +public: + StyleSheetPointer() = default; + StyleSheetPointer(const StyleSheetPointer &other) = delete; + StyleSheetPointer &operator=(const StyleSheetPointer &other) = delete; + + void createIfNull() { + if (!_p) { + _p = new T(); + internal::registerStyleSheet(&_p); + } + } + T *operator->() { + t_assert(_p != nullptr); + return static_cast(_p); + } + +private: + StyleSheet *_p; + +}; + +void clearStyleSheets(); + +} // namespace Layout +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_list.cpp new file mode 100644 index 000000000..12591987a --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_list.cpp @@ -0,0 +1,239 @@ +/* +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 +*/ +#include "stdafx.h" +#include "dialogs/dialogs_list.h" + +#include "dialogs/dialogs_layout.h" +#include "mainwidget.h" + +namespace Dialogs { + +List::List(SortMode sortMode) +: _last(std_::make_unique(nullptr, nullptr, nullptr, 0)) +, _begin(_last.get()) +, _end(_last.get()) +, _sortMode(sortMode) +, _current(_last.get()) { +} + +void List::adjustCurrent(int32 y, int32 h) const { + if (isEmpty()) return; + + int32 pos = (y > 0) ? (y / h) : 0; + while (_current->_pos > pos && _current != _begin) { + _current = _current->_prev; + } + while (_current->_pos + 1 <= pos && _current->_next != _end) { + _current = _current->_next; + } +} + +void List::paint(Painter &p, int32 w, int32 hFrom, int32 hTo, PeerData *act, PeerData *sel, bool onlyBackground) const { + adjustCurrent(hFrom, st::dlgHeight); + + Row *row = _current; + p.translate(0, row->_pos * st::dlgHeight); + while (row != _end && row->_pos * st::dlgHeight < hTo) { + bool active = (row->history()->peer == act) || (row->history()->peer->migrateTo() && row->history()->peer->migrateTo() == act); + bool selected = (row->history()->peer == sel); + Layout::RowPainter::paint(p, row, w, active, selected, onlyBackground); + row = row->_next; + p.translate(0, st::dlgHeight); + } +} + +Row *List::addToEnd(History *history) { + Row *result = new Row(history, _end->_prev, _end, _end->_pos); + _end->_pos++; + if (_begin == _end) { + _begin = _current = result; + } else { + _end->_prev->_next = result; + } + _rowByPeer.insert(history->peer->id, result); + ++_count; + _end->_prev = result; + if (_sortMode == SortMode::Date) { + adjustByPos(result); + } + return result; +} + +bool List::insertBefore(Row *row, Row *before) { + if (row == before) return false; + + if (_current == row) { + _current = row->_prev; + } + + Row *updateTill = row->_prev; + remove(row); + + // insert row + row->_next = before; // update row + row->_prev = before->_prev; + row->_next->_prev = row; // update row->next + if (row->_prev) { // update row->prev + row->_prev->_next = row; + } else { + _begin = row; + } + + // update y + for (Row *n = row; n != updateTill; n = n->_next) { + n->_next->_pos++; + row->_pos--; + } + return true; +} + +bool List::insertAfter(Row *row, Row *after) { + if (row == after) return false; + + if (_current == row) { + _current = row->_next; + } + + Row *updateFrom = row->_next; + remove(row); + + // insert row + row->_prev = after; // update row + row->_next = after->_next; + row->_prev->_next = row; // update row->prev + row->_next->_prev = row; // update row->next + + // update y + for (Row *n = updateFrom; n != row; n = n->_next) { + n->_pos--; + row->_pos++; + } + return true; +} + +Row *List::adjustByName(const PeerData *peer) { + if (_sortMode != SortMode::Name) return nullptr; + + auto i = _rowByPeer.find(peer->id); + if (i == _rowByPeer.cend()) return nullptr; + + Row *row = i.value(), *change = row; + while (change->_prev && change->_prev->history()->peer->name > peer->name) { + change = change->_prev; + } + if (!insertBefore(row, change)) { + while (change->_next != _end && change->_next->history()->peer->name < peer->name) { + change = change->_next; + } + insertAfter(row, change); + } + return row; +} + +Row *List::addByName(History *history) { + if (_sortMode != SortMode::Name) return nullptr; + + Row *row = addToEnd(history), *change = row; + const QString &peerName(history->peer->name); + while (change->_prev && change->_prev->history()->peer->name.compare(peerName, Qt::CaseInsensitive) > 0) { + change = change->_prev; + } + if (!insertBefore(row, change)) { + while (change->_next != _end && change->_next->history()->peer->name.compare(peerName, Qt::CaseInsensitive) < 0) { + change = change->_next; + } + insertAfter(row, change); + } + return row; +} + +void List::adjustByPos(Row *row) { + if (_sortMode != SortMode::Date || !_begin) return; + + Row *change = row; + if (change != _begin && _begin->history()->sortKeyInChatList() < row->history()->sortKeyInChatList()) { + change = _begin; + } 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()->sortKeyInChatList() > row->history()->sortKeyInChatList()) { + change = _end->_prev; + } else { + while (change->_next != _end && change->_next->history()->sortKeyInChatList() > row->history()->sortKeyInChatList()) { + change = change->_next; + } + } + insertAfter(row, change); + } +} + +bool List::del(PeerId peerId, Row *replacedBy) { + auto i = _rowByPeer.find(peerId); + if (i == _rowByPeer.cend()) return false; + + Row *row = i.value(); + if (App::main()) { + emit App::main()->dialogRowReplaced(row, replacedBy); + } + + if (row == _current) { + _current = row->_next; + } + for (Row *change = row->_next; change != _end; change = change->_next) { + --change->_pos; + } + --_end->_pos; + remove(row); + delete row; + --_count; + _rowByPeer.erase(i); + + return true; +} + +void List::remove(Row *row) { + row->_next->_prev = row->_prev; // update row->next + if (row->_prev) { // update row->prev + row->_prev->_next = row->_next; + } else { + _begin = row->_next; + } +} + +void List::clear() { + while (_begin != _end) { + _current = _begin; + _begin = _begin->_next; + delete _current; + } + _current = _begin; + _rowByPeer.clear(); + _count = 0; +} + +List::~List() { + clear(); +} + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.h b/Telegram/SourceFiles/dialogs/dialogs_list.h new file mode 100644 index 000000000..422eeb203 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_list.h @@ -0,0 +1,136 @@ +/* +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 + +#include "dialogs/dialogs_row.h" + +class PeerData; +namespace Dialogs { + +class List { +public: + List(SortMode sortMode); + List(const List &other) = delete; + List &operator=(const List &other) = delete; + + int size() const { + return _count; + } + bool isEmpty() const { + return size() == 0; + } + bool contains(PeerId peerId) const { + return _rowByPeer.contains(peerId); + } + Row *getRow(PeerId peerId) const { + return _rowByPeer.value(peerId); + } + Row *rowAtY(int32 y, int32 h) const { + auto i = cfind(y, h); + if (i == cend() || (*i)->pos() != ((y > 0) ? (y / h) : 0)) { + return nullptr; + } + return *i; + } + + void paint(Painter &p, int32 w, int32 hFrom, int32 hTo, PeerData *act, PeerData *sel, bool onlyBackground) const; + Row *addToEnd(History *history); + bool insertBefore(Row *row, Row *before); + bool insertAfter(Row *row, Row *after); + Row *adjustByName(const PeerData *peer); + Row *addByName(History *history); + void adjustByPos(Row *row); + bool del(PeerId peerId, Row *replacedBy = nullptr); + void remove(Row *row); + void clear(); + + class const_iterator { + public: + using value_type = Row*; + using pointer = Row**; + using reference = Row*&; + + explicit const_iterator(Row *p) : _p(p) { + } + inline Row* operator*() const { return _p; } + inline Row* const* operator->() const { return &_p; } + inline bool operator==(const const_iterator &other) const { return _p == other._p; } + inline bool operator!=(const const_iterator &other) const { return !(*this == other); } + inline const_iterator &operator++() { _p = next(_p); return *this; } + inline const_iterator operator++(int) { const_iterator result(*this); ++(*this); return result; } + inline const_iterator &operator--() { _p = prev(_p); return *this; } + inline const_iterator operator--(int) { const_iterator result(*this); --(*this); return result; } + inline const_iterator operator+(int j) const { const_iterator result = *this; return result += j; } + inline const_iterator operator-(int j) const { const_iterator result = *this; return result -= j; } + inline const_iterator &operator+=(int j) { if (j < 0) return (*this -= (-j)); while (j--) ++*this; return *this; } + inline const_iterator &operator-=(int j) { if (j < 0) return (*this += (-j)); while (j--) --*this; return *this; } + + private: + Row *_p; + friend class List; + + }; + friend class const_iterator; + using iterator = const_iterator; + + const_iterator cbegin() const { return const_iterator(_begin); } + const_iterator cend() const { return const_iterator(_end); } + const_iterator begin() const { return cbegin(); } + const_iterator end() const { return cend(); } + iterator begin() { return iterator(_begin); } + iterator end() { return iterator(_end); } + const_iterator cfind(Row *value) const { return value ? const_iterator(value) : cend(); } + const_iterator find(Row *value) const { return cfind(value); } + iterator find(Row *value) { return value ? iterator(value) : end(); } + const_iterator cfind(int y, int h) const { + adjustCurrent(y, h); + return iterator(_current); + } + const_iterator find(int y, int h) const { return cfind(y, h); } + iterator find(int y, int h) { + adjustCurrent(y, h); + return iterator(_current); + } + + ~List(); + +private: + void adjustCurrent(int y, int h) const; + static Row *next(Row *row) { + return row->_next; + } + static Row *prev(Row *row) { + return row->_prev; + } + + std_::unique_ptr _last; + Row *_begin; + Row *_end; + SortMode _sortMode; + int _count = 0; + + typedef QHash RowByPeer; + RowByPeer _rowByPeer; + + mutable Row *_current; // cache +}; + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h new file mode 100644 index 000000000..88dd3ca6f --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_row.h @@ -0,0 +1,78 @@ +/* +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 + +#include "ui/text.h" + +class History; +class HistoryItem; + +namespace Dialogs { +namespace Layout { +class RowPainter; +} // namespace Layout + +class List; +class Row { +public: + Row(History *history, Row *prev, Row *next, int pos) + : _history(history) + , _prev(prev) + , _next(next) + , _pos(pos) { + } + void *attached = nullptr; // for any attached data, for example View in contacts list + + History *history() const { + return _history; + } + int pos() const { + return _pos; + } + +private: + friend class List; + + History *_history; + Row *_prev, *_next; + int _pos; + +}; + +class FakeRow { +public: + FakeRow(HistoryItem *item) : _item(item) { + } + + HistoryItem *item() const { + return _item; + } + +private: + friend class Layout::RowPainter; + + HistoryItem *_item; + mutable const HistoryItem *_cacheFor = nullptr; + mutable Text _cache = Text{ int(st::dlgRichMinWidth) }; + +}; + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 15c6d61b4..1f0aa382f 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -19,65 +19,55 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" -#include "lang.h" +#include "dialogswidget.h" +#include "dialogs/dialogs_indexed_list.h" +#include "dialogs/dialogs_layout.h" +#include "ui/style.h" +#include "lang.h" #include "application.h" -#include "window.h" +#include "mainwindow.h" #include "dialogswidget.h" #include "mainwidget.h" #include "boxes/addcontactbox.h" #include "boxes/contactsbox.h" #include "boxes/confirmbox.h" - #include "localstorage.h" +#include "apiwrap.h" DialogsInner::DialogsInner(QWidget *parent, MainWidget *main) : SplittedWidget(parent) -, dialogs(DialogsSortByDate) -, contactsNoDialogs(DialogsSortByName) -, contacts(DialogsSortByName) -, sel(0) -, contactSel(false) -, selByMouse(false) -, _hashtagSel(-1) -, _filteredSel(-1) -, _searchedCount(0) -, _searchedMigratedCount(0) -, _searchedSel(-1) -, _peopleSel(-1) -, _lastSearchDate(0) -, _lastSearchPeer(0) -, _lastSearchId(0) -, _lastSearchMigratedId(0) -, _state(DefaultState) +, dialogs(std_::make_unique(Dialogs::SortMode::Date)) +, contactsNoDialogs(std_::make_unique(Dialogs::SortMode::Name)) +, contacts(std_::make_unique(Dialogs::SortMode::Name)) , _addContactLnk(this, lang(lng_add_contact_button)) -, _cancelSearchInPeer(this, st::btnCancelSearch) -, _overDelete(false) -, _searchInPeer(0) -, _searchInMigrated(0) -, _menuPeer(0) -, _menuActionPeer(0) -, _menu(0) { +, _cancelSearchInPeer(this, st::btnCancelSearch) { + if (Global::DialogsModeEnabled()) { + importantDialogs = std_::make_unique(Dialogs::SortMode::Date); + } connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); connect(main, SIGNAL(peerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&))); connect(main, SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(onPeerPhotoChanged(PeerData*))); - connect(main, SIGNAL(dialogRowReplaced(DialogRow*,DialogRow*)), this, SLOT(onDialogRowReplaced(DialogRow*,DialogRow*))); + connect(main, SIGNAL(dialogRowReplaced(Dialogs::Row*,Dialogs::Row*)), this, SLOT(onDialogRowReplaced(Dialogs::Row*,Dialogs::Row*))); connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact())); connect(&_cancelSearchInPeer, SIGNAL(clicked()), this, SIGNAL(cancelSearchInPeer())); _cancelSearchInPeer.hide(); refresh(); } -int32 DialogsInner::filteredOffset() const { +int DialogsInner::dialogsOffset() const { + return importantDialogs ? st::dlgImportantHeight : 0; +} + +int DialogsInner::filteredOffset() const { return _hashtagResults.size() * st::mentionHeight; } -int32 DialogsInner::peopleOffset() const { +int DialogsInner::peopleOffset() const { return filteredOffset() + (_filterResults.size() * st::dlgHeight) + st::searchedBarHeight; } -int32 DialogsInner::searchedOffset() const { - int32 result = peopleOffset() + (_peopleResults.isEmpty() ? 0 : ((_peopleResults.size() * st::dlgHeight) + st::searchedBarHeight)); +int DialogsInner::searchedOffset() const { + int result = peopleOffset() + (_peopleResults.isEmpty() ? 0 : ((_peopleResults.size() * st::dlgHeight) + st::searchedBarHeight)); if (_searchInPeer) result += st::dlgHeight; return result; } @@ -94,18 +84,22 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO } if (_state == DefaultState) { - int32 otherStart = dialogs.list.count * st::dlgHeight; - PeerData *active = App::main()->activePeer(), *selected = _menuPeer ? _menuPeer : (sel ? sel->history->peer : 0); - if (otherStart) { - dialogs.list.paint(p, fullWidth(), r.top(), r.top() + r.height(), active, selected, paintingOther); + QRect dialogsClip = r; + if (importantDialogs) { + Dialogs::Layout::paintImportantSwitch(p, Global::DialogsMode(), fullWidth(), _importantSwitchSel, paintingOther); + dialogsClip.translate(0, -st::dlgImportantHeight); + p.translate(0, st::dlgImportantHeight); } - if (contactsNoDialogs.list.count && false) { - contactsNoDialogs.list.paint(p, fullWidth(), r.top() - otherStart, r.top() + r.height() - otherStart, active, selected, paintingOther); - } else if (!otherStart) { - p.fillRect(r, st::white->b); + int32 otherStart = shownDialogs()->size() * st::dlgHeight; + PeerData *active = App::main()->activePeer(), *selected = _menuPeer ? _menuPeer : (_sel ? _sel->history()->peer : 0); + if (otherStart) { + shownDialogs()->all().paint(p, fullWidth(), dialogsClip.top(), dialogsClip.top() + dialogsClip.height(), active, selected, paintingOther); + } + if (!otherStart) { + p.fillRect(dialogsClip, st::white); if (!paintingOther) { - p.setFont(st::noContactsFont->f); - p.setPen(st::noContactsColor->p); + p.setFont(st::noContactsFont); + p.setPen(st::noContactsColor); p.drawText(QRect(0, 0, fullWidth(), st::noContactsHeight - (cContactsReceived() ? st::noContactsFont->height : 0)), lang(cContactsReceived() ? lng_no_chats : lng_contacts_loading), style::al_center); } } @@ -163,9 +157,9 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO PeerData *act = App::main()->activePeer(); MsgId actId = App::main()->activeMsgId(); for (; from < to; ++from) { - bool active = ((_filterResults[from]->history->peer == act) || (_filterResults[from]->history->peer->migrateTo() && _filterResults[from]->history->peer->migrateTo() == act)) && !actId; - bool selected = (from == _filteredSel) || (_filterResults[from]->history->peer == _menuPeer); - _filterResults[from]->paint(p, w, active, selected, paintingOther); + bool active = ((_filterResults[from]->history()->peer == act) || (_filterResults[from]->history()->peer->migrateTo() && _filterResults[from]->history()->peer->migrateTo() == act)) && !actId; + bool selected = (from == _filteredSel) || (_filterResults[from]->history()->peer == _menuPeer); + Dialogs::Layout::RowPainter::paint(p, _filterResults[from], w, active, selected, paintingOther); p.translate(0, st::dlgHeight); } } @@ -230,9 +224,12 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO PeerData *act = App::main()->activePeer(); MsgId actId = App::main()->activeMsgId(); for (; from < to; ++from) { - bool active = (_searchResults[from]->_item->history()->peer == act && _searchResults[from]->_item->id == actId) || (_searchResults[from]->_item->history()->peer->migrateTo() && _searchResults[from]->_item->history()->peer->migrateTo() == act && _searchResults[from]->_item->id == -actId); + auto result = _searchResults[from]; + auto item = result->item(); + auto history = item->history(); + bool active = (history->peer == act && item->id == actId) || (history->peer->migrateTo() && history->peer->migrateTo() == act && item->id == -actId); bool selected = (from == _searchedSel); - _searchResults[from]->paint(p, w, active, selected, paintingOther); + Dialogs::Layout::RowPainter::paint(p, result, w, active, selected, paintingOther); p.translate(0, st::dlgHeight); } } @@ -240,13 +237,11 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO } } -void DialogsInner::peopleResultPaint(PeerData *peer, Painter &p, int32 w, bool act, bool sel, bool onlyBackground) const { +void DialogsInner::peopleResultPaint(PeerData *peer, Painter &p, int32 w, bool active, bool selected, bool onlyBackground) const { QRect fullRect(0, 0, w, st::dlgHeight); - p.fillRect(fullRect, (act ? st::dlgActiveBG : (sel ? st::dlgHoverBG : st::dlgBG))->b); + p.fillRect(fullRect, active ? st::dlgActiveBG : (selected ? st::dlgHoverBG : st::dlgBG)); if (onlyBackground) return; - History *history = App::history(peer->id); - PeerData *userpicPeer = (peer->migrateTo() ? peer->migrateTo() : peer); userpicPeer->paintUserpicLeft(p, st::dlgPhotoSize, st::dlgPaddingHor, st::dlgPaddingVer, fullWidth()); @@ -256,21 +251,21 @@ void DialogsInner::peopleResultPaint(PeerData *peer, Painter &p, int32 w, bool a // draw chat icon 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)); + p.drawSprite(QPoint(rectForName.left() + st::dlgChatImgPos.x(), rectForName.top() + st::dlgChatImgPos.y()), (active ? st::dlgActiveChatImg : st::dlgChatImg)); rectForName.setLeft(rectForName.left() + st::dlgImgSkip); } else if (peer->isChannel()) { - p.drawPixmap(QPoint(rectForName.left() + st::dlgChannelImgPos.x(), rectForName.top() + st::dlgChannelImgPos.y()), App::sprite(), (act ? st::dlgActiveChannelImg : st::dlgChannelImg)); + p.drawSprite(QPoint(rectForName.left() + st::dlgChannelImgPos.x(), rectForName.top() + st::dlgChannelImgPos.y()), (active ? st::dlgActiveChannelImg : st::dlgChannelImg)); rectForName.setLeft(rectForName.left() + st::dlgImgSkip); } if (peer->isVerified()) { rectForName.setWidth(rectForName.width() - st::verifiedCheck.pxWidth() - st::verifiedCheckPos.x()); - p.drawSprite(rectForName.topLeft() + QPoint(qMin(peer->dialogName().maxWidth(), rectForName.width()), 0) + st::verifiedCheckPos, (act ? st::verifiedCheckInv : st::verifiedCheck)); + p.drawSprite(rectForName.topLeft() + QPoint(qMin(peer->dialogName().maxWidth(), rectForName.width()), 0) + st::verifiedCheckPos, (active ? st::verifiedCheckInv : st::verifiedCheck)); } QRect tr(nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgSep, namewidth, st::dlgFont->height); p.setFont(st::dlgHistFont->f); QString username = peer->userName(); - if (!act && username.toLower().startsWith(_peopleQuery)) { + if (!active && username.toLower().startsWith(_peopleQuery)) { QString first = '@' + username.mid(0, _peopleQuery.size()), second = username.mid(_peopleQuery.size()); int32 w = st::dlgHistFont->width(first); if (w >= tr.width()) { @@ -283,11 +278,11 @@ void DialogsInner::peopleResultPaint(PeerData *peer, Painter &p, int32 w, bool a p.drawText(tr.left() + w, tr.top() + st::dlgHistFont->ascent, st::dlgHistFont->elided(second, tr.width() - w)); } } else { - p.setPen((act ? st::dlgActiveColor : st::dlgSystemColor)->p); + p.setPen((active ? st::dlgActiveColor : st::dlgSystemColor)->p); p.drawText(tr.left(), tr.top() + st::dlgHistFont->ascent, st::dlgHistFont->elided('@' + username, tr.width())); } - p.setPen((act ? st::dlgActiveColor : st::dlgNameColor)->p); + p.setPen((active ? st::dlgActiveColor : st::dlgNameColor)->p); peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); } @@ -325,30 +320,26 @@ void DialogsInner::activate() { void DialogsInner::mouseMoveEvent(QMouseEvent *e) { lastMousePos = mapToGlobal(e->pos()); - selByMouse = true; + _selByMouse = true; onUpdateSelected(true); } void DialogsInner::onUpdateSelected(bool force) { QPoint mouse(mapFromGlobal(lastMousePos)); - if ((!force && !rect().contains(mouse)) || !selByMouse) return; + if ((!force && !rect().contains(mouse)) || !_selByMouse) return; int w = width(), mouseY = mouse.y(); _overDelete = false; if (_state == DefaultState) { - DialogRow *newSel = dialogs.list.rowAtY(mouseY, st::dlgHeight); - int32 otherStart = dialogs.list.count * st::dlgHeight; - if (newSel) { - contactSel = false; - } else { - newSel = 0;// contactsNoDialogs.list.rowAtY(mouseY - otherStart, st::dlgHeight); - contactSel = true; - } - if (newSel != sel) { + auto newImportantSwitchSel = (importantDialogs && mouseY >= 0 && mouseY < dialogsOffset()); + mouseY -= dialogsOffset(); + auto newSel = newImportantSwitchSel ? nullptr : shownDialogs()->rowAtY(mouseY, st::dlgHeight); + if (newSel != _sel || newImportantSwitchSel != _importantSwitchSel) { updateSelectedRow(); - sel = newSel; + _sel = newSel; + _importantSwitchSel = newImportantSwitchSel; updateSelectedRow(); - setCursor(sel ? style::cur_pointer : style::cur_default); + setCursor(_sel ? style::cur_pointer : style::cur_default); } } else if (_state == FilteredState || _state == SearchedState) { if (!_hashtagResults.isEmpty()) { @@ -407,7 +398,7 @@ void DialogsInner::onUpdateSelected(bool force) { void DialogsInner::mousePressEvent(QMouseEvent *e) { lastMousePos = mapToGlobal(e->pos()); - selByMouse = true; + _selByMouse = true; onUpdateSelected(true); if (e->button() == Qt::LeftButton) { choosePeer(); @@ -419,7 +410,7 @@ void DialogsInner::resizeEvent(QResizeEvent *e) { _cancelSearchInPeer.move(width() - st::dlgPaddingHor - st::btnCancelSearch.width, (st::dlgHeight - st::btnCancelSearch.height) / 2); } -void DialogsInner::onDialogRowReplaced(DialogRow *oldRow, DialogRow *newRow) { +void DialogsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow) { if (_state == FilteredState || _state == SearchedState) { for (FilteredDialogs::iterator i = _filterResults.begin(); i != _filterResults.end();) { if (*i == oldRow) { // this row is shown in filtered and maybe is in contacts! @@ -434,25 +425,47 @@ void DialogsInner::onDialogRowReplaced(DialogRow *oldRow, DialogRow *newRow) { } } } - if (sel == oldRow) { - sel = newRow; + if (_sel == oldRow) { + _sel = newRow; } } void DialogsInner::createDialog(History *history) { - bool creating = !history->inChatList(); + bool creating = !history->inChatList(Dialogs::Mode::All); if (creating) { - DialogRow *mainRow = history->addToChatList(dialogs); - contactsNoDialogs.del(history->peer, mainRow); + Dialogs::Row *mainRow = history->addToChatList(Dialogs::Mode::All, dialogs.get()); + contactsNoDialogs->del(history->peer, mainRow); + } + if (importantDialogs && !history->inChatList(Dialogs::Mode::Important) && !history->mute()) { + if (Global::DialogsMode() == Dialogs::Mode::Important) { + creating = true; + } + history->addToChatList(Dialogs::Mode::Important, importantDialogs.get()); } - RefPair(int32, movedFrom, int32, movedTo) = history->adjustByPosInChatsList(dialogs); - emit dialogMoved(movedFrom, movedTo); + auto changed = history->adjustByPosInChatList(Dialogs::Mode::All, dialogs.get()); + + if (importantDialogs) { + if (history->mute()) { + if (Global::DialogsMode() == Dialogs::Mode::Important) { + return; + } + } else { + auto importantChanged = history->adjustByPosInChatList(Dialogs::Mode::Important, importantDialogs.get()); + if (Global::DialogsMode() == Dialogs::Mode::Important) { + changed = importantChanged; + } + } + } + + int from = dialogsOffset() + changed.movedFrom * st::dlgHeight; + int to = dialogsOffset() + changed.movedTo * st::dlgHeight; + emit dialogMoved(from, to); if (creating) { refresh(); - } else if (_state == DefaultState && movedFrom != movedTo) { - update(0, qMin(movedFrom, movedTo), fullWidth(), qAbs(movedFrom - movedTo) + st::dlgHeight); + } else if (_state == DefaultState && changed.movedFrom != changed.movedTo) { + update(0, qMin(from, to), fullWidth(), qAbs(from - to) + st::dlgHeight); } } @@ -461,15 +474,18 @@ void DialogsInner::removeDialog(History *history) { if (history->peer == _menuPeer && _menu) { _menu->deleteLater(); } - if (sel && sel->history == history) { - sel = 0; + if (_sel && _sel->history() == history) { + _sel = nullptr; + } + history->removeFromChatList(Dialogs::Mode::All, dialogs.get()); + if (importantDialogs) { + history->removeFromChatList(Dialogs::Mode::Important, importantDialogs.get()); } - history->removeFromChatList(dialogs); history->clearNotifications(); if (App::wnd()) App::wnd()->notifyClear(history); - if (contacts.list.rowByPeer.constFind(history->peer->id) != contacts.list.rowByPeer.cend()) { - if (contactsNoDialogs.list.rowByPeer.constFind(history->peer->id) == contactsNoDialogs.list.rowByPeer.cend()) { - contactsNoDialogs.addByName(history); + if (contacts->contains(history->peer->id)) { + if (!contactsNoDialogs->contains(history->peer->id)) { + contactsNoDialogs->addByName(history); } } @@ -480,14 +496,18 @@ void DialogsInner::removeDialog(History *history) { refresh(); } -void DialogsInner::dlgUpdated(DialogRow *row) { +void DialogsInner::dlgUpdated(Dialogs::Mode list, Dialogs::Row *row) { if (_state == DefaultState) { - update(0, row->pos * st::dlgHeight, fullWidth(), st::dlgHeight); + if (Global::DialogsMode() == list) { + update(0, dialogsOffset() + row->pos() * st::dlgHeight, fullWidth(), st::dlgHeight); + } } else if (_state == FilteredState || _state == SearchedState) { - for (int32 i = 0, l = _filterResults.size(); i < l; ++i) { - if (_filterResults.at(i)->history == row->history) { - update(0, i * st::dlgHeight, fullWidth(), st::dlgHeight); - break; + if (list == Dialogs::Mode::All) { + for (int32 i = 0, l = _filterResults.size(); i < l; ++i) { + if (_filterResults.at(i)->history() == row->history()) { + update(0, i * st::dlgHeight, fullWidth(), st::dlgHeight); + break; + } } } } @@ -495,15 +515,13 @@ void DialogsInner::dlgUpdated(DialogRow *row) { void DialogsInner::dlgUpdated(History *history, MsgId msgId) { if (_state == DefaultState) { - DialogRow *row = 0; - DialogsList::RowByPeer::iterator i = dialogs.list.rowByPeer.find(history->peer->id); - if (i != dialogs.list.rowByPeer.cend()) { - update(0, i.value()->pos * st::dlgHeight, fullWidth(), st::dlgHeight); + if (auto row = shownDialogs()->getRow(history->peer->id)) { + update(0, dialogsOffset() + row->pos() * st::dlgHeight, fullWidth(), st::dlgHeight); } } else if (_state == FilteredState || _state == SearchedState) { int32 cnt = 0, add = filteredOffset(); for (FilteredDialogs::const_iterator i = _filterResults.cbegin(), e = _filterResults.cend(); i != e; ++i) { - if ((*i)->history == history) { + if ((*i)->history() == history) { update(0, add + cnt * st::dlgHeight, fullWidth(), st::dlgHeight); break; } @@ -522,7 +540,7 @@ void DialogsInner::dlgUpdated(History *history, MsgId msgId) { if (!_searchResults.isEmpty()) { int32 cnt = 0, add = searchedOffset(); for (SearchResults::const_iterator i = _searchResults.cbegin(), e = _searchResults.cend(); i != e; ++i) { - if ((*i)->_item->history() == history && (*i)->_item->id == msgId) { + if ((*i)->item()->history() == history && (*i)->item()->id == msgId) { update(0, add + cnt * st::dlgHeight, fullWidth(), st::dlgHeight); break; } @@ -542,17 +560,19 @@ void DialogsInner::updateSelectedRow(PeerData *peer) { if (_state == DefaultState) { if (peer) { if (History *h = App::historyLoaded(peer->id)) { - if (h->inChatList()) { - update(0, h->posInChatList() * st::dlgHeight, fullWidth(), st::dlgHeight); + if (h->inChatList(Global::DialogsMode())) { + update(0, dialogsOffset() + h->posInChatList(Global::DialogsMode()) * st::dlgHeight, fullWidth(), st::dlgHeight); } } - } else if (sel) { - update(0, sel->pos * st::dlgHeight, fullWidth(), st::dlgHeight); + } else if (_sel) { + update(0, dialogsOffset() + _sel->pos() * st::dlgHeight, fullWidth(), st::dlgHeight); + } else if (_importantSwitchSel) { + update(0, 0, fullWidth(), st::dlgImportantHeight); } } else if (_state == FilteredState || _state == SearchedState) { if (peer) { for (int32 i = 0, l = _filterResults.size(); i != l; ++i) { - if (_filterResults.at(i)->history->peer == peer) { + if (_filterResults.at(i)->history()->peer == peer) { update(0, filteredOffset() + i * st::dlgHeight, fullWidth(), st::dlgHeight); break; } @@ -572,10 +592,15 @@ void DialogsInner::updateSelectedRow(PeerData *peer) { void DialogsInner::leaveEvent(QEvent *e) { setMouseTracking(false); - selByMouse = false; - if (sel || _filteredSel >= 0 || _hashtagSel >= 0 || _searchedSel >= 0 || _peopleSel >= 0) { + clearSelection(); +} + +void DialogsInner::clearSelection() { + _selByMouse = false; + if (_importantSwitchSel || _sel || _filteredSel >= 0 || _hashtagSel >= 0 || _searchedSel >= 0 || _peopleSel >= 0) { updateSelectedRow(); - sel = 0; + _sel = nullptr; + _importantSwitchSel = false; _filteredSel = _searchedSel = _peopleSel = _hashtagSel = -1; setCursor(style::cur_default); } @@ -594,16 +619,16 @@ void DialogsInner::contextMenuEvent(QContextMenuEvent *e) { if (e->reason() == QContextMenuEvent::Mouse) { lastMousePos = e->globalPos(); - selByMouse = true; + _selByMouse = true; onUpdateSelected(true); } History *history = 0; if (_state == DefaultState) { - if (sel) history = sel->history; + if (_sel) history = _sel->history(); } else if (_state == FilteredState || _state == SearchedState) { if (_filteredSel >= 0 && _filteredSel < _filterResults.size()) { - history = _filterResults[_filteredSel]->history; + history = _filterResults[_filteredSel]->history(); } } if (!history) return; @@ -716,7 +741,7 @@ void DialogsInner::onMenuDestroyed(QObject *obj) { } lastMousePos = QCursor::pos(); if (rect().contains(mapFromGlobal(lastMousePos))) { - selByMouse = true; + _selByMouse = true; setMouseTracking(true); onUpdateSelected(true); } @@ -732,9 +757,12 @@ void DialogsInner::onParentGeometryChanged() { } void DialogsInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { - dialogs.peerNameChanged(peer, oldNames, oldChars); - contactsNoDialogs.peerNameChanged(peer, oldNames, oldChars); - contacts.peerNameChanged(peer, oldNames, oldChars); + dialogs->peerNameChanged(Dialogs::Mode::All, peer, oldNames, oldChars); + if (importantDialogs) { + importantDialogs->peerNameChanged(Dialogs::Mode::Important, peer, oldNames, oldChars); + } + contactsNoDialogs->peerNameChanged(peer, oldNames, oldChars); + contacts->peerNameChanged(peer, oldNames, oldChars); update(); } @@ -775,35 +803,36 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) { _state = FilteredState; _filterResults.clear(); if (!_searchInPeer && !f.isEmpty()) { - DialogsList *dialogsToFilter = 0, *contactsNoDialogsToFilter = 0; - if (dialogs.list.count) { + const Dialogs::List *toFilter = nullptr; + if (!dialogs->isEmpty()) { for (fi = fb; fi != fe; ++fi) { - DialogsIndexed::DialogsIndex::iterator i = dialogs.index.find(fi->at(0)); - if (i == dialogs.index.cend()) { - dialogsToFilter = 0; + auto found = dialogs->filtered(fi->at(0)); + if (found->isEmpty()) { + toFilter = nullptr; break; } - if (!dialogsToFilter || dialogsToFilter->count > i.value()->count) { - dialogsToFilter = i.value(); + if (!toFilter || toFilter->size() > found->size()) { + toFilter = found; } } } - if (contactsNoDialogs.list.count) { + const Dialogs::List *toFilterContacts = nullptr; + if (!contactsNoDialogs->isEmpty()) { for (fi = fb; fi != fe; ++fi) { - DialogsIndexed::DialogsIndex::iterator i = contactsNoDialogs.index.find(fi->at(0)); - if (i == contactsNoDialogs.index.cend()) { - contactsNoDialogsToFilter = 0; + auto found = contactsNoDialogs->filtered(fi->at(0)); + if (found->isEmpty()) { + toFilterContacts = nullptr; break; } - if (!contactsNoDialogsToFilter || contactsNoDialogsToFilter->count > i.value()->count) { - contactsNoDialogsToFilter = i.value(); + if (!toFilterContacts || toFilterContacts->size() > found->size()) { + toFilterContacts = found; } } } - _filterResults.reserve((dialogsToFilter ? dialogsToFilter->count : 0) + (contactsNoDialogsToFilter ? contactsNoDialogsToFilter->count : 0)); - if (dialogsToFilter && dialogsToFilter->count) { - for (DialogRow *i = dialogsToFilter->begin, *e = dialogsToFilter->end; i != e; i = i->next) { - const PeerData::Names &names(i->history->peer->names); + _filterResults.reserve((toFilter ? toFilter->size() : 0) + (toFilterContacts ? toFilterContacts->size() : 0)); + if (toFilter) { + for_const (auto row, *toFilter) { + const PeerData::Names &names(row->history()->peer->names); PeerData::Names::const_iterator nb = names.cbegin(), ne = names.cend(), ni; for (fi = fb; fi != fe; ++fi) { QString filterName(*fi); @@ -817,13 +846,13 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) { } } if (fi == fe) { - _filterResults.push_back(i); + _filterResults.push_back(row); } } } - if (contactsNoDialogsToFilter && contactsNoDialogsToFilter->count) { - for (DialogRow *i = contactsNoDialogsToFilter->begin, *e = contactsNoDialogsToFilter->end; i != e; i = i->next) { - const PeerData::Names &names(i->history->peer->names); + if (toFilterContacts) { + for_const (auto row, *toFilterContacts) { + const PeerData::Names &names(row->history()->peer->names); PeerData::Names::const_iterator nb = names.cbegin(), ne = names.cend(), ni; for (fi = fb; fi != fe; ++fi) { QString filterName(*fi); @@ -837,7 +866,7 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) { } } if (fi == fe) { - _filterResults.push_back(i); + _filterResults.push_back(row); } } } @@ -913,17 +942,17 @@ void DialogsInner::peerUpdated(PeerData *peer) { PeerData *DialogsInner::updateFromParentDrag(QPoint globalPos) { lastMousePos = globalPos; - selByMouse = true; + _selByMouse = true; onUpdateSelected(true); if (_state == DefaultState) { - if (sel) return sel->history->peer; + if (_sel) return _sel->history()->peer; } else if (_state == FilteredState || _state == SearchedState) { if (_filteredSel >= 0 && _filteredSel < _filterResults.size()) { - return _filterResults[_filteredSel]->history->peer; + return _filterResults[_filteredSel]->history()->peer; } else if (_peopleSel >= 0 && _peopleSel < _peopleResults.size()) { return _peopleResults[_peopleSel]; } else if (_searchedSel >= 0 && _searchedSel < _searchResults.size()) { - return _searchResults[_searchedSel]->_item->history()->peer; + return _searchResults[_searchedSel]->item()->history()->peer; } } return 0; @@ -932,7 +961,7 @@ PeerData *DialogsInner::updateFromParentDrag(QPoint globalPos) { void DialogsInner::itemRemoved(HistoryItem *item) { int wasCount = _searchResults.size(); for (int i = 0; i < _searchResults.size();) { - if (_searchResults[i]->_item == item) { + if (_searchResults[i]->item() == item) { _searchResults.remove(i); if (item->history()->peer == _searchInMigrated) { if (_searchedMigratedCount > 0) --_searchedMigratedCount; @@ -953,7 +982,7 @@ void DialogsInner::dialogsReceived(const QVector &added) { History *history = 0; switch (i->type()) { case mtpc_dialog: { - const MTPDdialog &d(i->c_dialog()); + const auto &d(i->c_dialog()); history = App::historyFromDialog(peerFromMTP(d.vpeer), d.vunread_count.v, d.vread_inbox_max_id.v); if (App::main()) { App::main()->applyNotifySetting(MTP_notifyPeer(d.vpeer), d.vnotify_settings, history); @@ -961,7 +990,7 @@ void DialogsInner::dialogsReceived(const QVector &added) { } break; case mtpc_dialogChannel: { - const MTPDdialogChannel &d(i->c_dialogChannel()); + const auto &d(i->c_dialogChannel()); PeerData *peer = App::peerLoaded(peerFromMTP(d.vpeer)); int32 unreadCount = (peer && peer->isMegagroup()) ? d.vunread_count.v : d.vunread_important_count.v; History *history = App::historyFromDialog(peerFromMTP(d.vpeer), unreadCount, d.vread_inbox_max_id.v); @@ -990,7 +1019,7 @@ void DialogsInner::dialogsReceived(const QVector &added) { if (!history->lastMsgDate.isNull()) { addSavedPeersAfter(history->lastMsgDate); } - contactsNoDialogs.del(history->peer); + contactsNoDialogs->del(history->peer); if (history->peer->migrateFrom()) { removeDialog(App::historyLoaded(history->peer->migrateFrom()->id)); } else if (history->peer->migrateTo() && history->peer->migrateTo()->amIn()) { @@ -1000,9 +1029,9 @@ void DialogsInner::dialogsReceived(const QVector &added) { } if (App::wnd()) App::wnd()->updateCounter(); - if (!sel && dialogs.list.count) { - sel = dialogs.list.begin; - contactSel = false; + if (!_sel && !shownDialogs()->isEmpty()) { + _sel = *shownDialogs()->cbegin(); + _importantSwitchSel = false; } refresh(); } @@ -1012,7 +1041,7 @@ void DialogsInner::addSavedPeersAfter(const QDateTime &date) { while (!saved.isEmpty() && (date.isNull() || date < saved.lastKey())) { History *history = App::history(saved.last()->id); history->setChatsListDate(saved.lastKey()); - contactsNoDialogs.del(history->peer); + contactsNoDialogs->del(history->peer); saved.remove(saved.lastKey(), saved.last()); } } @@ -1030,7 +1059,7 @@ bool DialogsInner::searchReceived(const QVector &messages, DialogsSe HistoryItem *item = App::histories().addNewMessage(*i, NewMessageExisting); int32 lastDate = dateFromMessage(*i); if (lastDate) { - _searchResults.push_back(new FakeDialogRow(item)); + _searchResults.push_back(new Dialogs::FakeRow(item)); lastDateFound = lastDate; if (type == DialogsSearchFromStart || type == DialogsSearchFromOffset) { _lastSearchDate = lastDateFound; @@ -1067,7 +1096,7 @@ void DialogsInner::peopleReceived(const QString &query, const QVector & for (QVector::const_iterator i = people.cbegin(), e = people.cend(); i != e; ++i) { PeerId peerId = peerFromMTP(*i); if (History *h = App::historyLoaded(peerId)) { - if (h->inChatList()) { + if (h->inChatList(Dialogs::Mode::All)) { continue; // skip existing chats } } @@ -1087,47 +1116,80 @@ void DialogsInner::contactsReceived(const QVector &contacts) { } } } - if (!sel && contactsNoDialogs.list.count && false) { - sel = contactsNoDialogs.list.begin; - contactSel = true; - } refresh(); } void DialogsInner::notify_userIsContactChanged(UserData *user, bool fromThisApp) { if (user->contact > 0) { History *history = App::history(user->id); - contacts.addByName(history); - DialogsList::RowByPeer::const_iterator i = dialogs.list.rowByPeer.constFind(user->id); - if (i == dialogs.list.rowByPeer.cend()) { - contactsNoDialogs.addByName(history); - } else if (fromThisApp) { - sel = i.value(); - contactSel = false; + contacts->addByName(history); + if (auto row = shownDialogs()->getRow(user->id)) { + if (fromThisApp) { + _sel = row; + _importantSwitchSel = false; + } + } else if (!dialogs->contains(user->id)) { + contactsNoDialogs->addByName(history); } } else { - if (sel && sel->history->peer == user) { - sel = 0; + if (_sel && _sel->history()->peer == user) { + _sel = nullptr; } - contactsNoDialogs.del(user); - contacts.del(user); + contactsNoDialogs->del(user); + contacts->del(user); } refresh(); } +void DialogsInner::notify_historyMuteUpdated(History *history) { + if (!importantDialogs || !history->inChatList(Dialogs::Mode::All)) return; + + if (history->mute()) { + if (_sel && _sel->history() == history && Global::DialogsMode() == Dialogs::Mode::Important) { + _sel = nullptr; + } + history->removeFromChatList(Dialogs::Mode::Important, importantDialogs.get()); + if (Global::DialogsMode() != Dialogs::Mode::Important) { + return; + } + refresh(); + } else { + bool creating = !history->inChatList(Dialogs::Mode::Important); + if (creating) { + history->addToChatList(Dialogs::Mode::Important, importantDialogs.get()); + } + + auto changed = history->adjustByPosInChatList(Dialogs::Mode::All, dialogs.get()); + + if (Global::DialogsMode() != Dialogs::Mode::Important) { + return; + } + + int from = dialogsOffset() + changed.movedFrom * st::dlgHeight; + int to = dialogsOffset() + changed.movedTo * st::dlgHeight; + emit dialogMoved(from, to); + + if (creating) { + refresh(); + } else if (_state == DefaultState && changed.movedFrom != changed.movedTo) { + update(0, qMin(from, to), fullWidth(), qAbs(from - to) + st::dlgHeight); + } + } +} + void DialogsInner::refresh(bool toTop) { int32 h = 0; if (_state == DefaultState) { - h = (dialogs.list.count/* + contactsNoDialogs.list.count*/) * st::dlgHeight; - if (h) { - if (!_addContactLnk.isHidden()) _addContactLnk.hide(); - } else { + if (shownDialogs()->isEmpty()) { h = st::noContactsHeight; if (cContactsReceived()) { if (_addContactLnk.isHidden()) _addContactLnk.show(); } else { if (!_addContactLnk.isHidden()) _addContactLnk.hide(); } + } else { + h = dialogsOffset() + shownDialogs()->size() * st::dlgHeight; + if (!_addContactLnk.isHidden()) _addContactLnk.hide(); } } else { if (!_addContactLnk.isHidden()) _addContactLnk.hide(); @@ -1146,11 +1208,11 @@ void DialogsInner::refresh(bool toTop) { } void DialogsInner::setMouseSel(bool msel, bool toTop) { - selByMouse = msel; - if (!selByMouse && toTop) { + _selByMouse = msel; + if (!_selByMouse && toTop) { if (_state == DefaultState) { - sel = (dialogs.list.count ? dialogs.list.begin : (contactsNoDialogs.list.count ? contactsNoDialogs.list.begin : 0)); - contactSel = !dialogs.list.count && contactsNoDialogs.list.count; + _sel = !shownDialogs()->isEmpty() ? *shownDialogs()->cbegin() : nullptr; + _importantSwitchSel = false; } else if (_state == FilteredState || _state == SearchedState) { // don't select first elem in search _filteredSel = _peopleSel = _searchedSel = _hashtagSel = -1; setCursor(style::cur_default); @@ -1212,31 +1274,39 @@ void DialogsInner::clearFilter() { void DialogsInner::selectSkip(int32 direction) { if (_state == DefaultState) { - if (!sel) { - if (dialogs.list.count && direction > 0) { - sel = dialogs.list.begin; - } else if (false && contactsNoDialogs.list.count && direction > 0) { - sel = contactsNoDialogs.list.begin; + if (_importantSwitchSel) { + if (!shownDialogs()->isEmpty() && direction > 0) { + _sel = *shownDialogs()->cbegin(); + _importantSwitchSel = false; + } else { + return; + } + } else if (!_sel) { + if (importantDialogs) { + _importantSwitchSel = true; + } else if (!shownDialogs()->isEmpty() && direction > 0) { + _sel = *shownDialogs()->cbegin(); } else { return; } } else if (direction > 0) { - if (sel->next->next) { - sel = sel->next; - } else if (false && sel->next == dialogs.list.end && contactsNoDialogs.list.count) { - sel = contactsNoDialogs.list.begin; - contactSel = true; + auto next = shownDialogs()->cfind(_sel); + if (++next != shownDialogs()->cend()) { + _sel = *next; } } else { - if (sel->prev) { - sel = sel->prev; - } else if (false && sel == contactsNoDialogs.list.begin && dialogs.list.count) { - sel = dialogs.list.end->prev; - contactSel = false; + auto prev = shownDialogs()->cfind(_sel); + if (prev != shownDialogs()->cbegin()) { + _sel = *(--prev); + } else if (importantDialogs) { + _importantSwitchSel = true; + _sel = nullptr; } } - int32 fromY = (sel->pos + (contactSel ? dialogs.list.count : 0)) * st::dlgHeight; - emit mustScrollTo(fromY, fromY + st::dlgHeight); + if (_importantSwitchSel || _sel) { + int fromY = _importantSwitchSel ? 0 : (dialogsOffset() + _sel->pos() * st::dlgHeight); + emit mustScrollTo(fromY, fromY + st::dlgHeight); + } } else if (_state == FilteredState || _state == SearchedState) { if (_hashtagResults.isEmpty() && _filterResults.isEmpty() && _peopleResults.isEmpty() && _searchResults.isEmpty()) return; if ((_hashtagSel < 0 || _hashtagSel >= _hashtagResults.size()) && @@ -1285,19 +1355,13 @@ void DialogsInner::selectSkip(int32 direction) { void DialogsInner::scrollToPeer(const PeerId &peer, MsgId msgId) { int32 fromY = -1; if (_state == DefaultState) { - DialogsList::RowByPeer::const_iterator i = dialogs.list.rowByPeer.constFind(peer); - if (i != dialogs.list.rowByPeer.cend()) { - fromY = i.value()->pos * st::dlgHeight; - } else if (false) { - i = contactsNoDialogs.list.rowByPeer.constFind(peer); - if (i != contactsNoDialogs.list.rowByPeer.cend()) { - fromY = (i.value()->pos + dialogs.list.count) * st::dlgHeight; - } + if (auto row = shownDialogs()->getRow(peer)) { + fromY = dialogsOffset() + row->pos() * st::dlgHeight; } } else if (_state == FilteredState || _state == SearchedState) { if (msgId) { for (int32 i = 0, c = _searchResults.size(); i < c; ++i) { - if (_searchResults[i]->_item->history()->peer->id == peer && _searchResults[i]->_item->id == msgId) { + if (_searchResults[i]->item()->history()->peer->id == peer && _searchResults[i]->item()->id == msgId) { fromY = searchedOffset() + i * st::dlgHeight; break; } @@ -1305,7 +1369,7 @@ void DialogsInner::scrollToPeer(const PeerId &peer, MsgId msgId) { } if (fromY < 0) { for (int32 i = 0, c = _filterResults.size(); i < c; ++i) { - if (_filterResults[i]->history->peer->id == peer) { + if (_filterResults[i]->history()->peer->id == peer) { fromY = filteredOffset() + (i * st::dlgHeight); break; } @@ -1318,42 +1382,33 @@ void DialogsInner::scrollToPeer(const PeerId &peer, MsgId msgId) { } void DialogsInner::selectSkipPage(int32 pixels, int32 direction) { - int32 toSkip = pixels / int32(st::dlgHeight); + int toSkip = pixels / int(st::dlgHeight); if (_state == DefaultState) { - if (!sel) { - if (direction > 0 && dialogs.list.count) { - sel = dialogs.list.begin; - } else if (false && direction > 0 && contactsNoDialogs.list.count) { - sel = contactsNoDialogs.list.begin; + if (!_sel) { + if (direction > 0 && !shownDialogs()->isEmpty()) { + _sel = *shownDialogs()->cbegin(); + _importantSwitchSel = false; } else { return; } } if (direction > 0) { - while (toSkip-- && sel->next->next) { - sel = sel->next; - } - if (false && toSkip >= 0 && sel->next == dialogs.list.end && contactsNoDialogs.list.count) { - sel = contactsNoDialogs.list.begin; - while (toSkip-- && sel->next->next) { - sel = sel->next; - } - contactSel = true; + for (auto i = shownDialogs()->cfind(_sel), end = shownDialogs()->cend(); i != end && (toSkip--); ++i) { + _sel = *i; } } else { - while (toSkip-- && sel->prev) { - sel = sel->prev; + for (auto i = shownDialogs()->cfind(_sel), b = shownDialogs()->cbegin(); i != b && (toSkip--); --i) { + _sel = *i; } - if (toSkip >= 0 && sel == contactsNoDialogs.list.begin && dialogs.list.count) { - sel = dialogs.list.end->prev; - while (toSkip-- && sel->prev) { - sel = sel->prev; - } - contactSel = false; + if (toSkip && importantDialogs) { + _importantSwitchSel = true; + _sel = nullptr; } } - int32 fromY = (sel->pos + (contactSel ? dialogs.list.count : 0)) * st::dlgHeight; - emit mustScrollTo(fromY, fromY + st::dlgHeight); + if (_importantSwitchSel || _sel) { + int fromY = (_importantSwitchSel ? 0 : (dialogsOffset() + _sel->pos() * st::dlgHeight)); + emit mustScrollTo(fromY, fromY + st::dlgHeight); + } } else { return selectSkip(direction * toSkip); } @@ -1366,23 +1421,19 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) { int32 yTo = yFrom + parentWidget()->height() * 5; MTP::clearLoaderPriorities(); if (_state == DefaultState) { - int32 otherStart = dialogs.list.count * st::dlgHeight; + int32 otherStart = shownDialogs()->size() * st::dlgHeight; 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->loadUserpic(); + for (auto i = shownDialogs()->cfind(yFrom, st::dlgHeight), end = shownDialogs()->cend(); i != end; ++i) { + if (((*i)->pos() * st::dlgHeight) >= yTo) { + break; + } + (*i)->history()->peer->loadUserpic(); } yFrom = 0; } else { yFrom -= otherStart; } yTo -= otherStart; - 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->loadUserpic(); - } - } } else if (_state == FilteredState || _state == SearchedState) { int32 from = (yFrom - filteredOffset()) / st::dlgHeight; if (from < 0) from = 0; @@ -1391,7 +1442,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) { if (to > _filterResults.size()) to = _filterResults.size(); for (; from < to; ++from) { - _filterResults[from]->history->peer->loadUserpic(); + _filterResults[from]->history()->peer->loadUserpic(); } } @@ -1412,17 +1463,30 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) { if (to > _searchResults.size()) to = _searchResults.size(); for (; from < to; ++from) { - _searchResults[from]->_item->history()->peer->loadUserpic(); + _searchResults[from]->item()->history()->peer->loadUserpic(); } } } } bool DialogsInner::choosePeer() { - History *history = 0; + History *history = nullptr; MsgId msgId = ShowAtUnreadMsgId; if (_state == DefaultState) { - if (sel) history = sel->history; + if (_importantSwitchSel && importantDialogs) { + clearSelection(); + if (Global::DialogsMode() == Dialogs::Mode::All) { + Global::SetDialogsMode(Dialogs::Mode::Important); + } else { + Global::SetDialogsMode(Dialogs::Mode::All); + } + Local::writeUserSettings(); + refresh(); + _importantSwitchSel = true; + return true; + } else if (_sel) { + history = _sel->history(); + } } else if (_state == FilteredState || _state == SearchedState) { if (_hashtagSel >= 0 && _hashtagSel < _hashtagResults.size()) { QString hashtag = _hashtagResults.at(_hashtagSel); @@ -1441,7 +1505,7 @@ bool DialogsInner::choosePeer() { Local::writeRecentHashtagsAndBots(); emit refreshHashtags(); - selByMouse = true; + _selByMouse = true; onUpdateSelected(true); } else { saveRecentHashtags('#' + hashtag); @@ -1450,12 +1514,12 @@ bool DialogsInner::choosePeer() { return true; } if (_filteredSel >= 0 && _filteredSel < _filterResults.size()) { - history = _filterResults[_filteredSel]->history; + history = _filterResults[_filteredSel]->history(); } else if (_peopleSel >= 0 && _peopleSel < _peopleResults.size()) { history = App::history(_peopleResults[_peopleSel]->id); } else if (_searchedSel >= 0 && _searchedSel < _searchResults.size()) { - history = _searchResults[_searchedSel]->_item->history(); - msgId = _searchResults[_searchedSel]->_item->id; + history = _searchResults[_searchedSel]->item()->history(); + msgId = _searchResults[_searchedSel]->item()->id; } } if (history) { @@ -1468,7 +1532,7 @@ bool DialogsInner::choosePeer() { emit searchResultChosen(); } updateSelectedRow(); - sel = 0; + _sel = nullptr; _filteredSel = _peopleSel = _searchedSel = _hashtagSel = -1; return true; } @@ -1504,8 +1568,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) { } void DialogsInner::destroyData() { - sel = 0; - contactSel = false; + _sel = nullptr; _hashtagSel = -1; _hashtagResults.clear(); _filteredSel = -1; @@ -1513,61 +1576,49 @@ void DialogsInner::destroyData() { _filter.clear(); _searchedSel = _peopleSel = -1; clearSearchResults(); - contacts.clear(); - contactsNoDialogs.clear(); - dialogs.clear(); + contacts = nullptr; + contactsNoDialogs = nullptr; + dialogs = nullptr; + if (importantDialogs) { + importantDialogs = nullptr; + } } void DialogsInner::peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg) const { if (!inPeer) { - outPeer = 0; + outPeer = nullptr; outMsg = 0; return; } if (_state == DefaultState) { - DialogsList::RowByPeer::const_iterator i = dialogs.list.rowByPeer.constFind(inPeer->id); - if (i == dialogs.list.rowByPeer.constEnd()) { - i = contactsNoDialogs.list.rowByPeer.constFind(inPeer->id); - if (i == contactsNoDialogs.list.rowByPeer.cend()) { - outPeer = 0; - outMsg = 0; - return; - } - if (i.value()->prev) { - outPeer = i.value()->prev->history->peer; - outMsg = ShowAtUnreadMsgId; - return; - } else if (dialogs.list.count) { - outPeer = dialogs.list.end->prev->history->peer; + if (auto row = shownDialogs()->getRow(inPeer->id)) { + auto i = shownDialogs()->cfind(row); + if (i != shownDialogs()->cbegin()) { + outPeer = (*(--i))->history()->peer; outMsg = ShowAtUnreadMsgId; return; } - outPeer = 0; - outMsg = 0; - return; - } - if (i.value()->prev) { - outPeer = i.value()->prev->history->peer; - outMsg = ShowAtUnreadMsgId; - return; } + outPeer = nullptr; + outMsg = 0; + return; } else if (_state == FilteredState || _state == SearchedState) { if (inMsg && !_searchResults.isEmpty()) { for (SearchResults::const_iterator b = _searchResults.cbegin(), i = b + 1, e = _searchResults.cend(); i != e; ++i) { - if ((*i)->_item->history()->peer == inPeer && (*i)->_item->id == inMsg) { + if ((*i)->item()->history()->peer == inPeer && (*i)->item()->id == inMsg) { SearchResults::const_iterator j = i - 1; - outPeer = (*j)->_item->history()->peer; - outMsg = (*j)->_item->id; + outPeer = (*j)->item()->history()->peer; + outMsg = (*j)->item()->id; return; } } - if (_searchResults.at(0)->_item->history()->peer == inPeer && _searchResults.at(0)->_item->id == inMsg) { + if (_searchResults.at(0)->item()->history()->peer == inPeer && _searchResults.at(0)->item()->id == inMsg) { outMsg = ShowAtUnreadMsgId; if (_peopleResults.isEmpty()) { if (_filterResults.isEmpty()) { - outPeer = 0; + outPeer = nullptr; } else { - outPeer = _filterResults.back()->history->peer; + outPeer = _filterResults.back()->history()->peer; } } else { outPeer = _peopleResults.back(); @@ -1576,7 +1627,7 @@ void DialogsInner::peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&ou } } if (!_peopleResults.isEmpty() && _peopleResults.at(0) == inPeer) { - outPeer = _filterResults.isEmpty() ? 0 : _filterResults.back()->history->peer; + outPeer = _filterResults.isEmpty() ? 0 : _filterResults.back()->history()->peer; outMsg = ShowAtUnreadMsgId; return; } @@ -1589,65 +1640,49 @@ void DialogsInner::peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&ou } } } - if (_filterResults.isEmpty() || _filterResults.at(0)->history->peer == inPeer) { - outPeer = 0; + if (_filterResults.isEmpty() || _filterResults.at(0)->history()->peer == inPeer) { + outPeer = nullptr; outMsg = 0; return; } for (FilteredDialogs::const_iterator b = _filterResults.cbegin(), i = b + 1, e = _filterResults.cend(); i != e; ++i) { - if ((*i)->history->peer == inPeer) { - outPeer = (*(i - 1))->history->peer; + if ((*i)->history()->peer == inPeer) { + outPeer = (*(i - 1))->history()->peer; outMsg = ShowAtUnreadMsgId; return; } } } - outPeer = 0; + outPeer = nullptr; outMsg = 0; } void DialogsInner::peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg) const { if (!inPeer) { - outPeer = 0; + outPeer = nullptr; outMsg = 0; return; } if (_state == DefaultState) { - DialogsList::RowByPeer::const_iterator i = dialogs.list.rowByPeer.constFind(inPeer->id); - if (i == dialogs.list.rowByPeer.constEnd()) { - //i = contactsNoDialogs.list.rowByPeer.constFind(inPeer->id); - //if (i == contactsNoDialogs.list.rowByPeer.cend()) { - // outPeer = 0; - // outMsg = 0; - // return; - //} - //if (i.value()->next != contactsNoDialogs.list.end) { - // outPeer = i.value()->next->history->peer; - // outMsg = ShowAtUnreadMsgId; - // return; - //} - outPeer = 0; - outMsg = 0; - return; - } - - if (i.value()->next != dialogs.list.end) { - outPeer = i.value()->next->history->peer; - outMsg = ShowAtUnreadMsgId; - return; - } else if (false && contactsNoDialogs.list.count) { - outPeer = contactsNoDialogs.list.begin->history->peer; - outMsg = ShowAtUnreadMsgId; - return; + if (auto row = shownDialogs()->getRow(inPeer->id)) { + auto i = shownDialogs()->cfind(row) + 1; + if (i != shownDialogs()->cend()) { + outPeer = (*i)->history()->peer; + outMsg = ShowAtUnreadMsgId; + return; + } } + outPeer = nullptr; + outMsg = 0; + return; } else if (_state == FilteredState || _state == SearchedState) { if (inMsg) { for (SearchResults::const_iterator i = _searchResults.cbegin(), e = _searchResults.cend(); i != e; ++i) { - if ((*i)->_item->history()->peer == inPeer && (*i)->_item->id == inMsg) { + if ((*i)->item()->history()->peer == inPeer && (*i)->item()->id == inMsg) { ++i; - outPeer = (i == e) ? 0 : (*i)->_item->history()->peer; - outMsg = (i == e) ? 0 : (*i)->_item->id; + outPeer = (i == e) ? nullptr : (*i)->item()->history()->peer; + outMsg = (i == e) ? 0 : (*i)->item()->id; return; } } @@ -1656,42 +1691,42 @@ void DialogsInner::peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&out if ((*i) == inPeer) { ++i; if (i == e && !_searchResults.isEmpty()) { - outPeer = _searchResults.front()->_item->history()->peer; - outMsg = _searchResults.front()->_item->id; + outPeer = _searchResults.front()->item()->history()->peer; + outMsg = _searchResults.front()->item()->id; } else { - outPeer = (i == e) ? 0 : (*i); + outPeer = (i == e) ? nullptr : (*i); outMsg = ShowAtUnreadMsgId; } return; } } for (FilteredDialogs::const_iterator i = _filterResults.cbegin(), e = _filterResults.cend(); i != e; ++i) { - if ((*i)->history->peer == inPeer) { + if ((*i)->history()->peer == inPeer) { ++i; if (i == e && !_peopleResults.isEmpty()) { outPeer = _peopleResults.front(); outMsg = ShowAtUnreadMsgId; } else if (i == e && !_searchResults.isEmpty()) { - outPeer = _searchResults.front()->_item->history()->peer; - outMsg = _searchResults.front()->_item->id; + outPeer = _searchResults.front()->item()->history()->peer; + outMsg = _searchResults.front()->item()->id; } else { - outPeer = (i == e) ? 0 : (*i)->history->peer; + outPeer = (i == e) ? nullptr : (*i)->history()->peer; outMsg = ShowAtUnreadMsgId; } return; } } } - outPeer = 0; + outPeer = nullptr; outMsg = 0; } -DialogsIndexed &DialogsInner::contactsList() { - return contacts; +Dialogs::IndexedList *DialogsInner::contactsList() { + return contacts.get(); } -DialogsIndexed &DialogsInner::dialogsList() { - return dialogs; +Dialogs::IndexedList *DialogsInner::dialogsList() { + return dialogs.get(); } DialogsInner::FilteredDialogs &DialogsInner::filteredList() { @@ -1791,19 +1826,19 @@ void DialogsWidget::activate() { } void DialogsWidget::createDialog(History *history) { - bool creating = !history->inChatList(); + bool creating = !history->inChatList(Dialogs::Mode::All); _inner.createDialog(history); if (creating && history->peer->migrateFrom()) { if (History *h = App::historyLoaded(history->peer->migrateFrom()->id)) { - if (h->inChatList()) { + if (h->inChatList(Dialogs::Mode::All)) { removeDialog(h); } } } } -void DialogsWidget::dlgUpdated(DialogRow *row) { - _inner.dlgUpdated(row); +void DialogsWidget::dlgUpdated(Dialogs::Mode list, Dialogs::Row *row) { + _inner.dlgUpdated(list, row); } void DialogsWidget::dlgUpdated(History *row, MsgId msgId) { @@ -1889,21 +1924,25 @@ void DialogsWidget::notify_userIsContactChanged(UserData *user, bool fromThisApp _inner.notify_userIsContactChanged(user, fromThisApp); } +void DialogsWidget::notify_historyMuteUpdated(History *history) { + _inner.notify_historyMuteUpdated(history); +} + void DialogsWidget::unreadCountsReceived(const QVector &dialogs) { for (QVector::const_iterator i = dialogs.cbegin(), e = dialogs.cend(); i != e; ++i) { switch (i->type()) { case mtpc_dialog: { - const MTPDdialog &d(i->c_dialog()); + const auto &d(i->c_dialog()); if (History *h = App::historyLoaded(peerFromMTP(d.vpeer))) { App::main()->applyNotifySetting(MTP_notifyPeer(d.vpeer), d.vnotify_settings, h); - if (d.vunread_count.v >= h->unreadCount) { + if (d.vunread_count.v >= h->unreadCount()) { h->setUnreadCount(d.vunread_count.v, false); h->inboxReadBefore = d.vread_inbox_max_id.v + 1; } } } break; case mtpc_dialogChannel: { - const MTPDdialogChannel &d(i->c_dialogChannel()); + const auto &d(i->c_dialogChannel()); if (History *h = App::historyLoaded(peerFromMTP(d.vpeer))) { if (h->peer->isChannel()) { h->peer->asChannel()->ptsReceived(d.vpts.v); @@ -1914,7 +1953,7 @@ void DialogsWidget::unreadCountsReceived(const QVector &dialogs) { } App::main()->applyNotifySetting(MTP_notifyPeer(d.vpeer), d.vnotify_settings, h); int32 unreadCount = h->isMegagroup() ? d.vunread_count.v : d.vunread_important_count.v; - if (unreadCount >= h->unreadCount) { + if (unreadCount >= h->unreadCount()) { h->setUnreadCount(unreadCount, false); h->inboxReadBefore = d.vread_inbox_max_id.v + 1; } @@ -1932,7 +1971,7 @@ void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpReque const QVector *m = 0; switch (dialogs.type()) { case mtpc_messages_dialogs: { - const MTPDmessages_dialogs &data(dialogs.c_messages_dialogs()); + const auto &data(dialogs.c_messages_dialogs()); App::feedUsers(data.vusers); App::feedChats(data.vchats); m = &data.vmessages.c_vector().v; @@ -1940,7 +1979,7 @@ void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpReque _dialogsFull = true; } break; case mtpc_messages_dialogsSlice: { - const MTPDmessages_dialogsSlice &data(dialogs.c_messages_dialogsSlice()); + const auto &data(dialogs.c_messages_dialogsSlice()); App::feedUsers(data.vusers); App::feedChats(data.vchats); m = &data.vmessages.c_vector().v; @@ -1966,7 +2005,7 @@ void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpReque for (int32 i = v->size(); i > 0;) { PeerId peer = 0; MsgId msgId = 0; - const MTPDialog &d(v->at(--i)); + const auto &d(v->at(--i)); switch (d.type()) { case mtpc_dialog: msgId = d.c_dialog().vtop_message.v; @@ -1984,7 +2023,7 @@ void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpReque if (msgId) { if (!lastMsgId) lastMsgId = msgId; for (int32 j = m->size(); j > 0;) { - const MTPMessage &d(m->at(--j)); + const auto &d(m->at(--j)); if (idFromMessage(d) == msgId && peerFromMessage(d) == peer) { int32 date = dateFromMessage(d); if (date) lastDate = date; @@ -2011,7 +2050,7 @@ void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpReque } bool DialogsWidget::dialogsFailed(const RPCError &error, mtpRequestId req) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; LOG(("RPC Error: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description())); if (_dialogsRequest == req) { @@ -2153,7 +2192,7 @@ void DialogsWidget::loadDialogs() { void DialogsWidget::contactsReceived(const MTPcontacts_Contacts &contacts) { cSetContactsReceived(true); if (contacts.type() == mtpc_contacts_contacts) { - const MTPDcontacts_contacts &d(contacts.c_contacts_contacts()); + const auto &d(contacts.c_contacts_contacts()); App::feedUsers(d.vusers); _inner.contactsReceived(d.vcontacts.c_vector().v); } @@ -2161,7 +2200,7 @@ void DialogsWidget::contactsReceived(const MTPcontacts_Contacts &contacts) { } bool DialogsWidget::contactsFailed(const RPCError &error) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; return true; } @@ -2180,10 +2219,10 @@ void DialogsWidget::searchReceived(DialogsSearchRequestType type, const MTPmessa if (_searchRequest == req) { switch (result.type()) { case mtpc_messages_messages: { - const MTPDmessages_messages &d(result.c_messages_messages()); + const auto &d(result.c_messages_messages()); App::feedUsers(d.vusers); App::feedChats(d.vchats); - const QVector &msgs(d.vmessages.c_vector().v); + const auto &msgs(d.vmessages.c_vector().v); if (!_inner.searchReceived(msgs, type, msgs.size())) { if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { _searchFullMigrated = true; @@ -2194,10 +2233,10 @@ void DialogsWidget::searchReceived(DialogsSearchRequestType type, const MTPmessa } break; case mtpc_messages_messagesSlice: { - const MTPDmessages_messagesSlice &d(result.c_messages_messagesSlice()); + const auto &d(result.c_messages_messagesSlice()); App::feedUsers(d.vusers); App::feedChats(d.vchats); - const QVector &msgs(d.vmessages.c_vector().v); + const auto &msgs(d.vmessages.c_vector().v); if (!_inner.searchReceived(msgs, type, d.vcount.v)) { if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { _searchFullMigrated = true; @@ -2208,7 +2247,7 @@ void DialogsWidget::searchReceived(DialogsSearchRequestType type, const MTPmessa } break; case mtpc_messages_channelMessages: { - const MTPDmessages_channelMessages &d(result.c_messages_channelMessages()); + const auto &d(result.c_messages_channelMessages()); if (_searchInPeer && _searchInPeer->isChannel()) { _searchInPeer->asChannel()->ptsReceived(d.vpts.v); } else { @@ -2220,7 +2259,7 @@ void DialogsWidget::searchReceived(DialogsSearchRequestType type, const MTPmessa App::feedUsers(d.vusers); App::feedChats(d.vchats); - const QVector &msgs(d.vmessages.c_vector().v); + const auto &msgs(d.vmessages.c_vector().v); if (!_inner.searchReceived(msgs, type, d.vcount.v)) { if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { _searchFullMigrated = true; @@ -2262,7 +2301,7 @@ void DialogsWidget::peopleReceived(const MTPcontacts_Found &result, mtpRequestId } bool DialogsWidget::searchFailed(DialogsSearchRequestType type, const RPCError &error, mtpRequestId req) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; if (_searchRequest == req) { _searchRequest = 0; @@ -2276,7 +2315,7 @@ bool DialogsWidget::searchFailed(DialogsSearchRequestType type, const RPCError & } bool DialogsWidget::peopleFailed(const RPCError &error, mtpRequestId req) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; if (_peopleRequest == req) { _peopleRequest = 0; @@ -2365,7 +2404,7 @@ void DialogsWidget::onListScroll() { if (_scroll.scrollTop() > (_inner.searchList().size() + _inner.filteredList().size() + _inner.peopleList().size()) * st::dlgHeight - PreloadHeightsCount * _scroll.height()) { onSearchMore(); } - } else if (_scroll.scrollTop() > _inner.dialogsList().list.count * st::dlgHeight - PreloadHeightsCount * _scroll.height()) { + } else if (_scroll.scrollTop() > _inner.dialogsList()->size() * st::dlgHeight - PreloadHeightsCount * _scroll.height()) { loadDialogs(); } } @@ -2541,11 +2580,11 @@ void DialogsWidget::removeDialog(History *history) { onFilterUpdate(); } -DialogsIndexed &DialogsWidget::contactsList() { +Dialogs::IndexedList *DialogsWidget::contactsList() { return _inner.contactsList(); } -DialogsIndexed &DialogsWidget::dialogsList() { +Dialogs::IndexedList *DialogsWidget::dialogsList() { return _inner.dialogsList(); } diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index 75ef10e49..fe5b33e62 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -21,6 +21,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once class MainWidget; +namespace Dialogs { +class Row; +class FakeRow; +class IndexedList; +} // namespace Dialogs enum DialogsSearchRequestType { DialogsSearchFromStart, @@ -49,10 +54,6 @@ public: void contactsReceived(const QVector &contacts); - int32 filteredOffset() const; - int32 peopleOffset() const; - int32 searchedOffset() const; - void mouseMoveEvent(QMouseEvent *e); void mousePressEvent(QMouseEvent *e); void resizeEvent(QResizeEvent *e); @@ -60,14 +61,11 @@ public: void leaveEvent(QEvent *e); void contextMenuEvent(QContextMenuEvent *e); - void peopleResultPaint(PeerData *peer, Painter &p, int32 w, bool act, bool sel, bool onlyBackground) const; - void searchInPeerPaint(Painter &p, int32 w, bool onlyBackground) const; - void selectSkip(int32 direction); void selectSkipPage(int32 pixels, int32 direction); void createDialog(History *history); - void dlgUpdated(DialogRow *row); + void dlgUpdated(Dialogs::Mode list, Dialogs::Row *row); void dlgUpdated(History *row, MsgId msgId); void removeDialog(History *history); @@ -84,12 +82,12 @@ public: void peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg) const; void scrollToPeer(const PeerId &peer, MsgId msgId); - typedef QVector FilteredDialogs; + typedef QVector FilteredDialogs; typedef QVector PeopleResults; - typedef QVector SearchResults; + typedef QVector SearchResults; - DialogsIndexed &contactsList(); - DialogsIndexed &dialogsList(); + Dialogs::IndexedList *contactsList(); + Dialogs::IndexedList *dialogsList(); FilteredDialogs &filteredList(); PeopleResults &peopleList(); SearchResults &searchList(); @@ -120,6 +118,7 @@ public: void updateNotifySettings(PeerData *peer); void notify_userIsContactChanged(UserData *user, bool fromThisApp); + void notify_historyMuteUpdated(History *history); ~DialogsInner(); @@ -129,7 +128,7 @@ public slots: void onParentGeometryChanged(); void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); void onPeerPhotoChanged(PeerData *peer); - void onDialogRowReplaced(DialogRow *oldRow, DialogRow *newRow); + void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow); void onContextProfile(); void onContextToggleNotifications(); @@ -160,51 +159,74 @@ protected: private: + int dialogsOffset() const; + int filteredOffset() const; + int peopleOffset() const; + int searchedOffset() const; + + void peopleResultPaint(PeerData *peer, Painter &p, int32 w, bool active, bool selected, bool onlyBackground) const; + void searchInPeerPaint(Painter &p, int32 w, bool onlyBackground) const; + + void clearSelection(); void clearSearchResults(bool clearPeople = true); void updateSelectedRow(PeerData *peer = 0); bool menuPeerMuted(); void contextBlockDone(QPair data, const MTPBool &result); - DialogsIndexed dialogs; - DialogsIndexed contactsNoDialogs; - DialogsIndexed contacts; - DialogRow *sel; - bool contactSel; - bool selByMouse; + Dialogs::IndexedList *shownDialogs() const { + return (Global::DialogsMode() == Dialogs::Mode::Important) ? importantDialogs.get() : dialogs.get(); + } + + using DialogsList = std_::unique_ptr; + DialogsList dialogs; + DialogsList importantDialogs; + + DialogsList contactsNoDialogs; + DialogsList contacts; + + bool _importantSwitchSel = false; + Dialogs::Row *_sel = nullptr; + bool _selByMouse = false; QString _filter, _hashtagFilter; QStringList _hashtagResults; - int32 _hashtagSel; + int _hashtagSel = -1; FilteredDialogs _filterResults; - int32 _filteredSel; + int _filteredSel = -1; SearchResults _searchResults; - int32 _searchedCount, _searchedMigratedCount, _searchedSel; + int _searchedCount = 0; + int _searchedMigratedCount = 0; + int _searchedSel = -1; QString _peopleQuery; PeopleResults _peopleResults; - int32 _peopleSel; + int _peopleSel = -1; - int32 _lastSearchDate; - PeerData *_lastSearchPeer; - MsgId _lastSearchId, _lastSearchMigratedId; + int _lastSearchDate = 0; + PeerData *_lastSearchPeer = nullptr; + MsgId _lastSearchId = 0; + MsgId _lastSearchMigratedId = 0; - State _state; + State _state = DefaultState; QPoint lastMousePos; - void paintDialog(QPainter &p, DialogRow *dialog); + void paintDialog(QPainter &p, Dialogs::Row *dialog); LinkButton _addContactLnk; IconedButton _cancelSearchInPeer; - bool _overDelete; + bool _overDelete = false; - PeerData *_searchInPeer, *_searchInMigrated, *_menuPeer, *_menuActionPeer; + PeerData *_searchInPeer = nullptr; + PeerData *_searchInMigrated = nullptr; + PeerData *_menuPeer = nullptr; + PeerData *_menuActionPeer = nullptr; - PopupMenu *_menu; + PopupMenu *_menu = nullptr; }; @@ -233,7 +255,7 @@ public: void loadDialogs(); void createDialog(History *history); - void dlgUpdated(DialogRow *row); + void dlgUpdated(Dialogs::Mode list, Dialogs::Row *row); void dlgUpdated(History *row, MsgId msgId); void dialogsToUp(); @@ -249,8 +271,8 @@ public: void removeDialog(History *history); - DialogsIndexed &contactsList(); - DialogsIndexed &dialogsList(); + Dialogs::IndexedList *contactsList(); + Dialogs::IndexedList *dialogsList(); void searchMessages(const QString &query, PeerData *inPeer = 0); void onSearchMore(); @@ -265,6 +287,7 @@ public: } void notify_userIsContactChanged(UserData *user, bool fromThisApp); + void notify_historyMuteUpdated(History *history); signals: diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index ee475348b..676ff0025 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -19,19 +19,18 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE 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 "mainwindow.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; @@ -1191,6 +1194,13 @@ void EmojiPanInner::step_selected(uint64 ms, bool timer) { if (_animations.isEmpty()) _a_selected.stop(); } +void InlineCacheEntry::clearResults() { + for_const (const InlineBots::Result *result, results) { + delete result; + } + results.clear(); +} + void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) { clearSelection(true); @@ -1256,6 +1266,9 @@ int32 StickerPanInner::countHeight(bool plain) { int result = 0, minLastH = plain ? 0 : (_maxHeight - st::stickerPanPadding); if (_showingInlineItems) { result = st::emojiPanHeader; + if (_switchPmButton) { + result += _switchPmButton->height() + st::inlineResultsSkip; + } for (int i = 0, l = _inlineRows.count(); i < l; ++i) { result += _inlineRows.at(i).height; } @@ -1270,6 +1283,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,26 +1328,34 @@ 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()); - for (int32 row = 0, rows = _inlineRows.size(); row < rows; ++row) { + int top = st::emojiPanHeader; + if (_switchPmButton) { + top += _switchPmButton->height() + st::inlineResultsSkip; + } + + int fromx = rtl() ? (width() - r.x() - r.width()) : r.x(), tox = rtl() ? (width() - r.x()) : (r.x() + r.width()); + for (int row = 0, rows = _inlineRows.size(); row < rows; ++row) { const InlineRow &inlineRow(_inlineRows.at(row)); if (top >= r.top() + r.height()) break; if (top + inlineRow.height > r.top()) { - int32 left = st::inlineResultsLeft; + int left = st::inlineResultsLeft; if (row == rows - 1) context.lastRow = true; - for (int32 col = 0, cols = inlineRow.items.size(); col < cols; ++col) { + for (int 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); + int 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), &context); p.translate(-left, -top); } - left += w + st::inlineResultsSkip; + left += w; + if (item->hasRightSkip()) { + left += st::inlineResultsSkip; + } } } top += inlineRow.height; @@ -1413,8 +1440,7 @@ void StickerPanInner::mousePressEvent(QMouseEvent *e) { updateSelected(); _pressedSel = _selected; - textlnkDown(textlnkOver()); - _linkDown = _linkOver; + ClickHandler::pressed(); _previewTimer.start(QApplication::startDragTime()); } @@ -1422,10 +1448,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 +1460,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; @@ -1578,11 +1568,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; @@ -1618,17 +1604,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) { @@ -1664,7 +1656,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 +1665,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; } @@ -1708,7 +1705,7 @@ void StickerPanInner::refreshSavedGifs() { } 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); @@ -1730,7 +1727,7 @@ void StickerPanInner::refreshSavedGifs() { void StickerPanInner::inlineBotChanged() { _setGifCommand = false; - refreshInlineRows(0, InlineResults(), true); + refreshInlineRows(nullptr, nullptr, true); deleteUnusedInlineLayouts(); } @@ -1752,37 +1749,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(); @@ -1790,8 +1783,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 { @@ -1808,8 +1801,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 { @@ -1826,11 +1819,11 @@ 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[SavedGifsMaxPerRow]; + int indices[InlineItemsMaxPerRow]; for (int i = 0; i < count; ++i) { indices[i] = i; } @@ -1839,7 +1832,7 @@ StickerPanInner::InlineRow &StickerPanInner::layoutInlineRow(InlineRow &row, int }); row.height = 0; - int availw = width() - st::inlineResultsLeft - st::inlineResultsSkip * (count - 1); + 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(); @@ -1848,6 +1841,10 @@ StickerPanInner::InlineRow &StickerPanInner::layoutInlineRow(InlineRow &row, int if (sumWidth) { availw -= actualw; sumWidth -= row.items.at(index)->maxWidth(); + if (index > 0 && row.items.at(index - 1)->hasRightSkip()) { + availw -= st::inlineResultsSkip; + sumWidth -= st::inlineResultsSkip; + } } } return row; @@ -1914,9 +1911,38 @@ void StickerPanInner::clearInlineRowsPanel() { clearInlineRows(false); } -int32 StickerPanInner::refreshInlineRows(UserData *bot, const InlineResults &results, bool resultsDeleted) { +void StickerPanInner::refreshSwitchPmButton(const InlineCacheEntry *entry) { + if (!entry || entry->switchPmText.isEmpty()) { + _switchPmButton.reset(); + _switchPmStartToken.clear(); + } else { + if (!_switchPmButton) { + _switchPmButton = std_::make_unique(this, QString(), st::switchPmButton); + _switchPmButton->show(); + _switchPmButton->move(st::inlineResultsLeft, st::emojiPanHeader); + connect(_switchPmButton.get(), SIGNAL(clicked()), this, SLOT(onSwitchPm())); + } + _switchPmButton->setText(entry->switchPmText); // doesn't perform text.toUpper() + _switchPmStartToken = entry->switchPmStartToken; + } + update(); +} + +int StickerPanInner::refreshInlineRows(UserData *bot, const InlineCacheEntry *entry, bool resultsDeleted) { _inlineBot = bot; - if (results.isEmpty() && (!_inlineBot || _inlineBot->username != cInlineGifBotUsername())) { + refreshSwitchPmButton(entry); + auto clearResults = [this, entry]() { + if (!entry) { + return true; + } + if (entry->results.isEmpty() && entry->switchPmText.isEmpty()) { + if (!_inlineBot || _inlineBot->username != cInlineGifBotUsername()) { + return true; + } + } + return false; + }; + if (clearResults()) { if (resultsDeleted) { clearInlineRows(true); } @@ -1933,15 +1959,15 @@ int32 StickerPanInner::refreshInlineRows(UserData *bot, const InlineResults &res _showingSavedGifs = false; _settings.hide(); - int32 count = results.size(), from = validateExistingInlineRows(results), added = 0; + int32 count = entry->results.size(), from = validateExistingInlineRows(entry->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)) { + if (inlineRowsAddItem(0, entry->results.at(i), row, sumWidth)) { ++added; } } @@ -1963,7 +1989,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; @@ -2011,7 +2037,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; } @@ -2020,7 +2046,20 @@ int32 StickerPanInner::validateExistingInlineRows(const InlineResults &results) return until; } -void StickerPanInner::ui_repaintInlineItem(const LayoutInlineItem *layout) { +void StickerPanInner::notify_inlineItemLayoutChanged(const InlineItem *layout) { + if (_selected < 0 || !_showingInlineItems) { + return; + } + + int32 row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; + if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { + if (layout == _inlineRows.at(row).items.at(col)) { + updateSelected(); + } + } +} + +void StickerPanInner::ui_repaintInlineItem(const InlineItem *layout) { uint64 ms = getms(); if (_lastScrolled + 100 <= ms) { update(); @@ -2029,7 +2068,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; @@ -2189,19 +2228,26 @@ 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)); 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; + int sx = (rtl() ? width() - p.x() : p.x()) - st::inlineResultsLeft; + int sy = p.y() - st::emojiPanHeader; + if (_switchPmButton) { + sy -= _switchPmButton->height() + st::inlineResultsSkip; + } + int row = -1, col = -1, sel = -1; + ClickHandlerPtr lnk; + ClickHandlerHost *lnkhost = nullptr; HistoryCursorState cursor = HistoryDefaultCursorState; if (sy >= 0) { row = 0; - for (int32 rows = _inlineRows.size(); row < rows; ++row) { + for (int rows = _inlineRows.size(); row < rows; ++row) { if (sy < _inlineRows.at(row).height) { break; } @@ -2216,11 +2262,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; } @@ -2241,28 +2291,18 @@ 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) { + auto layout = _inlineRows.at(row).items.at(col); + if (auto previewDocument = layout->getPreviewDocument()) { + Ui::showMediaPreview(previewDocument); + } else if (auto previewPhoto = layout->getPreviewPhoto()) { + Ui::showMediaPreview(previewPhoto); + } } } } - 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; } @@ -2342,7 +2382,7 @@ void StickerPanInner::updateSelected() { if (_pressedSel >= 0 && _selected >= 0 && _pressedSel != _selected) { _pressedSel = _selected; if (newSel >= 0 && xNewSel < 0) { - Ui::showStickerPreview(_sets.at(newSelTab).pack.at(newSel % MatrixRowShift)); + Ui::showMediaPreview(_sets.at(newSelTab).pack.at(newSel % MatrixRowShift)); } } if (startanim && !_a_selected.animating()) _a_selected.start(); @@ -2356,14 +2396,20 @@ 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()) { + auto layout = _inlineRows.at(row).items.at(col); + if (auto previewDocument = layout->getPreviewDocument()) { + Ui::showMediaPreview(previewDocument); + _previewShown = true; + } else if (auto previewPhoto = layout->getPreviewPhoto()) { + Ui::showMediaPreview(previewPhoto); + _previewShown = true; + } } } else if (_pressedSel < MatrixRowShift * _sets.size()) { int tab = (_pressedSel / MatrixRowShift), sel = _pressedSel % MatrixRowShift; if (sel < _sets.at(tab).pack.size()) { - Ui::showStickerPreview(_sets.at(tab).pack.at(sel)); + Ui::showMediaPreview(_sets.at(tab).pack.at(sel)); _previewShown = true; } } @@ -2380,6 +2426,13 @@ void StickerPanInner::onUpdateInlineItems() { } } +void StickerPanInner::onSwitchPm() { + if (_inlineBot && _inlineBot->botInfo) { + _inlineBot->botInfo->startToken = _switchPmStartToken; + Ui::showPeerHistory(_inlineBot, ShowAndStartBotMsgId); + } +} + void StickerPanInner::step_selected(uint64 ms, bool timer) { QRegion toUpdate; for (Animations::iterator i = _animations.begin(); i != _animations.end();) { @@ -2578,6 +2631,8 @@ void EmojiSwitchButton::paintEvent(QPaintEvent *e) { } } +} // namespace internal + EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) , _maxHeight(st::emojiPanMaxHeight) , _contentMaxHeight(st::emojiPanMaxHeight) @@ -2672,7 +2727,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())); @@ -2812,7 +2867,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)); @@ -3357,13 +3412,19 @@ void EmojiPan::stickersInstalled(uint64 setId) { showStart(); } -void EmojiPan::ui_repaintInlineItem(const LayoutInlineItem *layout) { +void EmojiPan::notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) { + if (_stickersShown && !isHidden()) { + s_inner.notify_inlineItemLayoutChanged(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); } @@ -3377,14 +3438,6 @@ bool EmojiPan::ui_isInlineItemBeingChosen() { return false; } -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(); - } - } -} - void EmojiPan::showAll() { if (_stickersShown) { s_scroll.show(); @@ -3439,7 +3492,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) { @@ -3631,7 +3684,7 @@ void EmojiPan::inlineBotChanged() { if (_inlineRequestId) MTP::cancel(_inlineRequestId); _inlineRequestId = 0; _inlineQuery = _inlineNextQuery = _inlineNextOffset = QString(); - _inlineBot = 0; + _inlineBot = nullptr; for (InlineCache::const_iterator i = _inlineCache.cbegin(), e = _inlineCache.cend(); i != e; ++i) { delete i.value(); } @@ -3650,86 +3703,28 @@ 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 QVector &v(d.vresults.c_vector().v); + const auto &d(result.c_messages_botResults()); + const auto &v(d.vresults.c_vector().v); uint64 queryId(d.vquery_id.v); if (!adding) { - it = _inlineCache.insert(_inlineQuery, new InlineCacheEntry()); + it = _inlineCache.insert(_inlineQuery, new internal::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()->switchPmStartToken = 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 auto &res, v) { + if (auto result = InlineBots::Result::create(queryId, res)) { ++added; - it.value()->results.push_back(result); + it.value()->results.push_back(result.release()); } } @@ -3747,20 +3742,27 @@ void EmojiPan::inlineResultsDone(const MTPmessages_BotResults &result) { } bool EmojiPan::inlineResultsFail(const RPCError &error) { - if (mtpIsFlood(error)) return false; - + // show error? Notify::inlineBotRequesting(false); _inlineRequestId = 0; return true; } -void EmojiPan::queryInlineBot(UserData *bot, QString query) { +void EmojiPan::queryInlineBot(UserData *bot, PeerData *peer, QString query) { bool force = false; + _inlineQueryPeer = peer; if (bot != _inlineBot) { 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); @@ -3779,7 +3781,7 @@ void EmojiPan::queryInlineBot(UserData *bot, QString query) { } void EmojiPan::onInlineRequest() { - if (_inlineRequestId || !_inlineBot) return; + if (_inlineRequestId || !_inlineBot || !_inlineQueryPeer) return; _inlineQuery = _inlineNextQuery; QString nextOffset; @@ -3789,7 +3791,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, _inlineQueryPeer->input, MTPInputGeoPoint(), MTP_string(_inlineQuery), MTP_string(nextOffset)), rpcDone(&EmojiPan::inlineResultsDone), rpcFail(&EmojiPan::inlineResultsFail)); } void EmojiPan::onEmptyInlineRows() { @@ -3803,16 +3806,18 @@ void EmojiPan::onEmptyInlineRows() { } bool EmojiPan::refreshInlineRows(int32 *added) { - bool clear = true; - InlineCache::const_iterator i = _inlineCache.constFind(_inlineQuery); + auto i = _inlineCache.constFind(_inlineQuery); + const internal::InlineCacheEntry *entry = nullptr; if (i != _inlineCache.cend()) { - clear = i.value()->results.isEmpty(); + if (!i.value()->results.isEmpty() || !i.value()->switchPmText.isEmpty()) { + entry = i.value(); + } _inlineNextOffset = i.value()->nextOffset; } - if (clear) prepareShowHideCache(); - int32 result = s_inner.refreshInlineRows(_inlineBot, clear ? InlineResults() : i.value()->results, false); + if (!entry) prepareShowHideCache(); + int32 result = s_inner.refreshInlineRows(_inlineBot, entry, false); if (added) *added = result; - return !clear; + return (entry != nullptr); } int32 EmojiPan::showInlineRows(bool newResults) { @@ -3830,7 +3835,7 @@ int32 EmojiPan::showInlineRows(bool newResults) { if (clear) { if (!hidden && hideOnNoInlineResults()) { hideAnimated(); - } else { + } else if (!_hiding) { _cache = QPixmap(); // clear after refreshInlineRows() } } else { @@ -4254,7 +4259,7 @@ void MentionsInner::onUpdateSelected(bool force) { if (_down >= 0 && _sel >= 0 && _down != _sel) { _down = _sel; if (_down >= 0 && _down < _srows->size()) { - Ui::showStickerPreview(_srows->at(_down)); + Ui::showMediaPreview(_srows->at(_down)); } } } @@ -4270,7 +4275,7 @@ void MentionsInner::onParentGeometryChanged() { void MentionsInner::onPreview() { if (_down >= 0 && _down < _srows->size()) { - Ui::showStickerPreview(_srows->at(_down)); + Ui::showMediaPreview(_srows->at(_down)); _previewShown = true; } } @@ -4501,7 +4506,7 @@ void MentionsDropdown::updateFiltered(bool resetScroll) { if (_channel->mgInfo->bots.isEmpty()) { if (!_channel->mgInfo->botStatus && App::api()) App::api()->requestBots(_channel); } else { - for_const (auto *user, _channel->mgInfo->bots) { + 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; diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index f4d18ae30..ba8eb213e 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -20,8 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "gui/twidget.h" -#include "gui/boxshadow.h" +#include "ui/twidget.h" +#include "ui/boxshadow.h" class Dropdown : public TWidget { Q_OBJECT @@ -157,8 +157,31 @@ 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; + +struct InlineCacheEntry { + ~InlineCacheEntry() { + clearResults(); + } + QString nextOffset; + QString switchPmText, switchPmStartToken; + InlineResults results; // owns this results list + void clearResults(); +}; class EmojiColorPicker : public TWidget { Q_OBJECT @@ -222,6 +245,7 @@ private: }; +class EmojiPanel; class EmojiPanInner : public TWidget { Q_OBJECT @@ -344,7 +368,7 @@ public: void refreshStickers(); void refreshRecentStickers(bool resize = true); void refreshSavedGifs(); - int32 refreshInlineRows(UserData *bot, const InlineResults &results, bool resultsDeleted); + int refreshInlineRows(UserData *bot, const InlineCacheEntry *results, bool resultsDeleted); void refreshRecent(); void inlineBotChanged(); void hideInlineRowsPanel(); @@ -359,8 +383,9 @@ public: uint64 currentSet(int yOffset) const; - void ui_repaintInlineItem(const LayoutInlineItem *layout); - bool ui_isInlineItemVisible(const LayoutInlineItem *layout); + void notify_inlineItemLayoutChanged(const InlineItem *layout); + void ui_repaintInlineItem(const InlineItem *layout); + bool ui_isInlineItemVisible(const InlineItem *layout); bool ui_isInlineItemBeingChosen(); bool inlineResultsShown() const { @@ -368,24 +393,21 @@ public: } int32 countHeight(bool plain = false); - ~StickerPanInner() { - clearInlineRows(true); - deleteUnusedGifLayouts(); - deleteUnusedInlineLayouts(); - } + ~StickerPanInner(); -public slots: +private slots: void updateSelected(); void onSettings(); void onPreview(); void onUpdateInlineItems(); + void onSwitchPm(); 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); @@ -406,13 +428,15 @@ private: void paintInlineItems(Painter &p, const QRect &r); void paintStickers(Painter &p, const QRect &r); - int32 _maxHeight; + void refreshSwitchPmButton(const InlineCacheEntry *entry); void appendSet(uint64 setId); void selectEmoji(EmojiPtr emoji); QRect stickerRect(int tab, int sel); + int32 _maxHeight; + typedef QMap Animations; // index - showing, -index - hiding Animations _animations; Animation _a_selected; @@ -439,7 +463,10 @@ private: QTimer _updateInlineItems; bool _inlineWithThumb; - typedef QVector InlineItems; + std_::unique_ptr _switchPmButton; + QString _switchPmStartToken; + + typedef QVector InlineItems; struct InlineRow { InlineRow() : height(0) { } @@ -450,13 +477,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 +496,6 @@ private: int32 validateExistingInlineRows(const InlineResults &results); int32 _selected, _pressedSel; QPoint _lastMousePos; - TextLinkPtr _linkOver, _linkDown; LinkButton _settings; @@ -532,6 +558,8 @@ protected: }; +} // namespace internal + class EmojiPan : public TWidget, public RPCSender { Q_OBJECT @@ -567,7 +595,7 @@ public: bool eventFilter(QObject *obj, QEvent *e); void stickersInstalled(uint64 setId); - void queryInlineBot(UserData *bot, QString query); + void queryInlineBot(UserData *bot, PeerData *peer, QString query); void clearInlineBot(); bool overlaps(const QRect &globalRect) { @@ -580,14 +608,14 @@ public: ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } - void ui_repaintInlineItem(const LayoutInlineItem *layout); - bool ui_isInlineItemVisible(const LayoutInlineItem *layout); + void notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout); + void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout); + bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout); bool ui_isInlineItemBeingChosen(); bool inlineResultsShown() const { return s_inner.inlineResultsShown(); } - void notify_automaticLoadSettingsChangedGif(); public slots: @@ -622,7 +650,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 +670,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 +689,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,33 +709,20 @@ 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; QTimer _saveConfigTimer; // inline bots - struct InlineCacheEntry { - ~InlineCacheEntry() { - clearResults(); - } - QString nextOffset; - InlineResults results; - void clearResults() { - for (int32 i = 0, l = results.size(); i < l; ++i) { - delete results.at(i); - } - results.clear(); - } - }; - typedef QMap InlineCache; + typedef QMap InlineCache; InlineCache _inlineCache; QTimer _inlineRequestTimer; @@ -717,6 +732,7 @@ private: void recountContentMaxHeight(); bool refreshInlineRows(int32 *added = 0); UserData *_inlineBot; + PeerData *_inlineQueryPeer = nullptr; QString _inlineQuery, _inlineNextQuery, _inlineNextOffset; mtpRequestId _inlineRequestId; void inlineResultsDone(const MTPmessages_BotResults &result); diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 0b9557e3d..60aa53e2e 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -20,455 +20,562 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "window.h" +#include "mainwindow.h" #include "mainwidget.h" #include "application.h" - +#include "core/click_handler_types.h" +#include "boxes/confirmbox.h" #include "layerwidget.h" #include "lang.h" -Q_DECLARE_METATYPE(TextLinkPtr); +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); - } - - bool insertBotCommand(const QString &cmd, bool specialGif) { - if (MainWidget *m = main()) return m->insertBotCommand(cmd, specialGif); - return false; - } - - void searchByHashtag(const QString &tag, PeerData *inPeer) { - if (MainWidget *m = main()) m->searchMessages(tag + ' ', (inPeer && inPeer->isChannel()) ? 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) { - if (MainWidget *m = main()) m->joinGroupByHash(hash); - } - - void stickersBox(const QString &name) { - if (MainWidget *m = main()) m->stickersBox(MTP_inputStickerSetShortName(MTP_string(name))); - } - - void openLocalUrl(const QString &url) { - if (MainWidget *m = main()) m->openLocalUrl(url); - } - - bool forward(const PeerId &peer, ForwardWhatMessages what) { - if (MainWidget *m = main()) return m->onForward(peer, what); - return false; - } - - void removeDialog(History *history) { - if (MainWidget *m = main()) { - m->removeDialog(history); - } - } - - void showSettings() { - if (Window *w = wnd()) { - w->showSettings(); - } - } - - void activateTextLink(TextLinkPtr link, Qt::MouseButton button) { - if (Window *w = wnd()) { - qRegisterMetaType(); - qRegisterMetaType(); - QMetaObject::invokeMethod(w, "app_activateTextLink", Qt::QueuedConnection, Q_ARG(TextLinkPtr, link), Q_ARG(Qt::MouseButton, button)); - } - } - +void sendBotCommand(PeerData *peer, const QString &cmd, MsgId replyTo) { + if (MainWidget *m = main()) m->sendBotCommand(peer, cmd, replyTo); } -namespace Ui { +bool insertBotCommand(const QString &cmd, bool specialGif) { + if (MainWidget *m = main()) return m->insertBotCommand(cmd, specialGif); + return false; +} - void showStickerPreview(DocumentData *sticker) { - if (Window *w = App::wnd()) { - w->ui_showStickerPreview(sticker); +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 auto &buttonRow(markup->rows.at(row)); + if (col < buttonRow.size()) { + button = &buttonRow.at(col); + } } } + if (!button) return; - void hideStickerPreview() { - if (Window *w = App::wnd()) { - w->ui_hideStickerPreview(); + 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; - void showLayer(LayeredWidget *box, ShowLayerOptions options) { - if (Window *w = App::wnd()) { - w->ui_showLayer(box, options); - } else { - delete box; - } - } + case HistoryMessageReplyMarkup::Button::Url: { + auto url = QString::fromUtf8(button->data); + HiddenUrlClickHandler(url).onClick(Qt::LeftButton); + } break; - void hideLayer(bool fast) { - if (Window *w = App::wnd()) w->ui_showLayer(0, ShowLayerOptions(CloseOtherLayers) | (fast ? ForceFastShowLayer : AnimatedShowLayer)); - } + case HistoryMessageReplyMarkup::Button::RequestLocation: { + Ui::showLayer(new InformBox(lang(lng_bot_share_location_unavailable))); + } break; - bool isLayerShown() { - if (Window *w = App::wnd()) return w->ui_isLayerShown(); - return false; - } + 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; - bool isMediaViewShown() { - if (Window *w = App::wnd()) return w->ui_isMediaViewShown(); - return false; - } - - bool isInlineItemBeingChosen() { - if (MainWidget *m = App::main()) return m->ui_isInlineItemBeingChosen(); - return false; - } - - void repaintHistoryItem(const HistoryItem *item) { - if (!item) return; - if (MainWidget *m = App::main()) m->ui_repaintHistoryItem(item); - } - - void repaintInlineItem(const LayoutInlineItem *layout) { - if (!layout) return; - if (MainWidget *m = App::main()) m->ui_repaintInlineItem(layout); - } - - bool isInlineItemVisible(const LayoutInlineItem *layout) { - if (MainWidget *m = App::main()) return m->ui_isInlineItemVisible(layout); - return false; - } - - void autoplayMediaInlineAsync(const FullMsgId &msgId) { + case HistoryMessageReplyMarkup::Button::SwitchInline: { 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); - } - - void showPeerHistoryAsync(const PeerId &peer, MsgId msgId) { - if (MainWidget *m = App::main()) { - QMetaObject::invokeMethod(m, "ui_showPeerHistoryAsync", Qt::QueuedConnection, Q_ARG(quint64, peer), Q_ARG(qint32, msgId)); - } - } - - 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; + if (UserData *bot = msg->history()->peer->asUser()) { + auto tryFastSwitch = [bot, &button]() -> bool { + if (bot->botInfo && bot->botInfo->inlineReturnPeerId) { + if (Notify::switchInlineBotButtonReceived(QString::fromUtf8(button->data))) { + return true; + } + } + return false; + }; + if (!tryFastSwitch()) { + m->inlineSwitchLayer('@' + bot->username + ' ' + QString::fromUtf8(button->data)); } } } - return false; + } break; } - } +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) { + if (MainWidget *m = main()) m->joinGroupByHash(hash); +} + +void stickersBox(const QString &name) { + if (MainWidget *m = main()) m->stickersBox(MTP_inputStickerSetShortName(MTP_string(name))); +} + +void openLocalUrl(const QString &url) { + if (MainWidget *m = main()) m->openLocalUrl(url); +} + +bool forward(const PeerId &peer, ForwardWhatMessages what) { + if (MainWidget *m = main()) return m->onForward(peer, what); + return false; +} + +void removeDialog(History *history) { + if (MainWidget *m = main()) { + m->removeDialog(history); + } +} + +void showSettings() { + if (auto w = wnd()) { + w->showSettings(); + } +} + +void activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button) { + if (auto w = wnd()) { + qRegisterMetaType(); + qRegisterMetaType(); + QMetaObject::invokeMethod(w, "app_activateClickHandler", Qt::QueuedConnection, Q_ARG(ClickHandlerPtr, handler), Q_ARG(Qt::MouseButton, button)); + } +} + +void logOutDelayed() { + if (auto w = App::wnd()) { + QMetaObject::invokeMethod(w, "onLogoutSure", Qt::QueuedConnection); + } +} + +} // namespace App + +namespace Ui { + +void showMediaPreview(DocumentData *document) { + if (auto w = App::wnd()) { + w->ui_showMediaPreview(document); + } +} + +void showMediaPreview(PhotoData *photo) { + if (auto w = App::wnd()) { + w->ui_showMediaPreview(photo); + } +} + +void hideMediaPreview() { + if (auto w = App::wnd()) { + w->ui_hideMediaPreview(); + } +} + +void showLayer(LayeredWidget *box, ShowLayerOptions options) { + if (auto w = App::wnd()) { + w->ui_showLayer(box, options); + } else { + delete box; + } +} + +void hideLayer(bool fast) { + if (auto w = App::wnd()) w->ui_showLayer(0, ShowLayerOptions(CloseOtherLayers) | (fast ? ForceFastShowLayer : AnimatedShowLayer)); +} + +bool isLayerShown() { + if (auto w = App::wnd()) return w->ui_isLayerShown(); + return false; +} + +bool isMediaViewShown() { + if (auto w = App::wnd()) return w->ui_isMediaViewShown(); + return false; +} + +bool isInlineItemBeingChosen() { + if (MainWidget *m = App::main()) return m->ui_isInlineItemBeingChosen(); + return false; +} + +void repaintHistoryItem(const HistoryItem *item) { + if (!item) return; + if (MainWidget *m = App::main()) m->ui_repaintHistoryItem(item); +} + +void repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { + if (!layout) return; + if (MainWidget *m = App::main()) m->ui_repaintInlineItem(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); +} + +void showPeerHistoryAsync(const PeerId &peer, MsgId msgId) { + if (MainWidget *m = App::main()) { + QMetaObject::invokeMethod(m, "ui_showPeerHistoryAsync", Qt::QueuedConnection, Q_ARG(quint64, peer), Q_ARG(qint32, msgId)); + } +} + +PeerData *getPeerForMouseAction() { + if (auto w = App::wnd()) { + return w->ui_getPeerForMouseAction(); + } + return nullptr; +} + +bool hideWindowNoQuit() { + if (!App::quitting()) { + if (auto 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 Ui + namespace Notify { - void userIsBotChanged(UserData *user) { - if (MainWidget *m = App::main()) m->notify_userIsBotChanged(user); - } - - void userIsContactChanged(UserData *user, bool fromThisApp) { - if (MainWidget *m = App::main()) m->notify_userIsContactChanged(user, fromThisApp); - } - - void botCommandsChanged(UserData *user) { - if (MainWidget *m = App::main()) m->notify_botCommandsChanged(user); - } - - void inlineBotRequesting(bool requesting) { - if (MainWidget *m = App::main()) m->notify_inlineBotRequesting(requesting); - } - - void migrateUpdated(PeerData *peer) { - if (MainWidget *m = App::main()) m->notify_migrateUpdated(peer); - } - - void clipStopperHidden(ClipStopperType type) { - if (MainWidget *m = App::main()) m->notify_clipStopperHidden(type); - } - - void historyItemLayoutChanged(const HistoryItem *item) { - if (MainWidget *m = App::main()) m->notify_historyItemLayoutChanged(item); - } - - void automaticLoadSettingsChangedGif() { - 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(); - } - +void userIsBotChanged(UserData *user) { + if (MainWidget *m = App::main()) m->notify_userIsBotChanged(user); } +void userIsContactChanged(UserData *user, bool fromThisApp) { + if (MainWidget *m = App::main()) m->notify_userIsContactChanged(user, fromThisApp); +} + +void botCommandsChanged(UserData *user) { + if (MainWidget *m = App::main()) m->notify_botCommandsChanged(user); +} + +void inlineBotRequesting(bool requesting) { + if (MainWidget *m = App::main()) m->notify_inlineBotRequesting(requesting); +} + +void replyMarkupUpdated(const HistoryItem *item) { + if (MainWidget *m = App::main()) { + m->notify_replyMarkupUpdated(item); + } +} + +void inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop) { + if (MainWidget *m = App::main()) { + m->notify_inlineKeyboardMoved(item, oldKeyboardTop, newKeyboardTop); + } +} + +bool switchInlineBotButtonReceived(const QString &query) { + if (MainWidget *m = App::main()) { + return m->notify_switchInlineBotButtonReceived(query); + } + return false; +} + +void migrateUpdated(PeerData *peer) { + if (MainWidget *m = App::main()) m->notify_migrateUpdated(peer); +} + +void clipStopperHidden(ClipStopperType type) { + if (MainWidget *m = App::main()) m->notify_clipStopperHidden(type); +} + +void historyItemLayoutChanged(const HistoryItem *item) { + if (MainWidget *m = App::main()) m->notify_historyItemLayoutChanged(item); +} + +void inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) { + if (MainWidget *m = App::main()) m->notify_inlineItemLayoutChanged(layout); +} + +void historyMuteUpdated(History *history) { + if (MainWidget *m = App::main()) m->notify_historyMuteUpdated(history); +} + +void handlePendingHistoryUpdate() { + if (MainWidget *m = App::main()) { + m->notify_handlePendingHistoryUpdate(); + } + for_const (HistoryItem *item, Global::PendingRepaintItems()) { + Ui::repaintHistoryItem(item); + } + Global::RefPendingRepaintItems().clear(); +} + +} // namespace Notify + #define DefineReadOnlyVar(Namespace, Type, Name) const Type &Name() { \ - t_assert_full(Namespace##Data != 0, #Namespace "Data is null in " #Namespace "::" #Name, __FILE__, __LINE__); \ + 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 is null in Global::Ref" #Name, __FILE__, __LINE__); \ + 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 is null in Global::Set" #Name, __FILE__, __LINE__); \ + t_assert_full(Namespace##Data != 0, #Namespace "Data != nullptr in " #Namespace "::Set" #Name, __FILE__, __LINE__); \ Namespace##Data->Name = Name; \ } namespace Sandbox { +namespace internal { - namespace internal { +struct Data { + QString LangSystemISO; + int32 LangSystem = languageDefault; - struct Data { - QString LangSystemISO; - int32 LangSystem = languageDefault; + QByteArray LastCrashDump; + ConnectionProxy PreLaunchProxy; +}; - QByteArray LastCrashDump; - ConnectionProxy PreLaunchProxy; - }; +} // namespace internal +} // namespace Sandbox - } - -} -Sandbox::internal::Data *SandboxData = 0; +Sandbox::internal::Data *SandboxData = nullptr; 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; - } +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"); } - 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)); - 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); - 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; - } + if (usertag.open(QIODevice::WriteOnly)) { + usertag.write(reinterpret_cast(&SandboxUserTag), sizeof(uint64)); 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; +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 Sandbox namespace Global { +namespace internal { - bool started() { - return GlobalData != 0; - } +struct Data { + uint64 LaunchId = 0; + SingleDelayedCall HandleHistoryUpdate = { App::app(), "call_handleHistoryUpdate" }; - void start() { - GlobalData = new internal::Data(); + Adaptive::Layout AdaptiveLayout = Adaptive::NormalLayout; + bool AdaptiveForWide = true; + bool DialogsModeEnabled = false; + Dialogs::Mode DialogsMode = Dialogs::Mode::All; - 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); + int32 DebugLoggingFlags = 0; // 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); + 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; - DefineVar(Global, HiddenPinnedMessagesMap, HiddenPinnedMessages); + HiddenPinnedMessagesMap HiddenPinnedMessages; - DefineRefVar(Global, PendingItemsMap, PendingRepaintItems); + PendingItemsMap PendingRepaintItems; - DefineVar(Global, Stickers::Sets, StickerSets); - DefineVar(Global, Stickers::Order, StickerSetsOrder); - DefineVar(Global, uint64, LastStickersUpdate); + Stickers::Sets StickerSets; + Stickers::Order StickerSetsOrder; + uint64 LastStickersUpdate = 0; - DefineVar(Global, MTP::DcOptions, DcOptions); - - DefineRefVar(Global, CircleMasksMap, CircleMasks); + MTP::DcOptions DcOptions; + CircleMasksMap CircleMasks; }; + +} // namespace internal +} // namespace Global + +Global::internal::Data *GlobalData = nullptr; + +namespace Global { + +bool started() { + return GlobalData != nullptr; +} + +void start() { + GlobalData = new internal::Data(); + + memset_rand(&GlobalData->LaunchId, sizeof(GlobalData->LaunchId)); +} + +void finish() { + delete GlobalData; + GlobalData = nullptr; +} + +DefineReadOnlyVar(Global, uint64, LaunchId); +DefineRefVar(Global, SingleDelayedCall, HandleHistoryUpdate); + +DefineVar(Global, Adaptive::Layout, AdaptiveLayout); +DefineVar(Global, bool, AdaptiveForWide); +DefineVar(Global, bool, DialogsModeEnabled); +DefineVar(Global, Dialogs::Mode, DialogsMode); + +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); + +} // namespace Global diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index ba77a20a2..a8bf6fbb1 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -24,58 +24,71 @@ class LayeredWidget; namespace App { - void sendBotCommand(const QString &cmd, MsgId replyTo = 0); - bool insertBotCommand(const QString &cmd, bool specialGif = false); - void searchByHashtag(const QString &tag, PeerData *inPeer); - 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); - bool forward(const PeerId &peer, ForwardWhatMessages what); - void removeDialog(History *history); - void showSettings(); +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, MsgId msgId = ShowAtUnreadMsgId, const QString &startToken = QString()); +void joinGroupByHash(const QString &hash); +void stickersBox(const QString &name); +void openLocalUrl(const QString &url); +bool forward(const PeerId &peer, ForwardWhatMessages what); +void removeDialog(History *history); +void showSettings(); - void activateTextLink(TextLinkPtr link, Qt::MouseButton button); +void activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button); -}; +void logOutDelayed(); + +} // namespace App + +namespace InlineBots { +namespace Layout { + +class ItemBase; + +} // namespace Layout +} // namespace InlineBots namespace Ui { - void showStickerPreview(DocumentData *sticker); - void hideStickerPreview(); +void showMediaPreview(DocumentData *document); +void showMediaPreview(PhotoData *photo); +void hideMediaPreview(); - void showLayer(LayeredWidget *box, ShowLayerOptions options = CloseOtherLayers); - void hideLayer(bool fast = false); - bool isLayerShown(); - bool isMediaViewShown(); - bool isInlineItemBeingChosen(); +void showLayer(LayeredWidget *box, ShowLayerOptions options = CloseOtherLayers); +void hideLayer(bool fast = false); +bool isLayerShown(); +bool isMediaViewShown(); +bool isInlineItemBeingChosen(); - void repaintHistoryItem(const HistoryItem *item); - void repaintInlineItem(const LayoutInlineItem *layout); - bool isInlineItemVisible(const LayoutInlineItem *reader); - void autoplayMediaInlineAsync(const FullMsgId &msgId); +void repaintHistoryItem(const HistoryItem *item); +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) { - showPeerHistory(peer->id, msgId, back); - } - inline void showPeerHistory(const History *history, MsgId msgId, bool back = false) { - showPeerHistory(history->peer->id, msgId, back); - } - inline void showPeerHistoryAtItem(const HistoryItem *item) { - showPeerHistory(item->history()->peer->id, item->id); - } - void showPeerHistoryAsync(const PeerId &peer, MsgId msgId); - inline void showChatsList() { - showPeerHistory(PeerId(0), 0); - } - inline void showChatsListAsync() { - showPeerHistoryAsync(PeerId(0), 0); - } +void showPeerHistory(const PeerId &peer, MsgId msgId, bool back = false); +inline void showPeerHistory(const PeerData *peer, MsgId msgId, bool back = false) { + showPeerHistory(peer->id, msgId, back); +} +inline void showPeerHistory(const History *history, MsgId msgId, bool back = false) { + showPeerHistory(history->peer->id, msgId, back); +} +inline void showPeerHistoryAtItem(const HistoryItem *item) { + showPeerHistory(item->history()->peer->id, item->id); +} +void showPeerHistoryAsync(const PeerId &peer, MsgId msgId); +inline void showChatsList() { + showPeerHistory(PeerId(0), 0); +} +inline void showChatsListAsync() { + showPeerHistoryAsync(PeerId(0), 0); +} +PeerData *getPeerForMouseAction(); - bool hideWindowNoQuit(); +bool hideWindowNoQuit(); -}; +} // namespace Ui enum ClipStopperType { ClipStopperMediaview, @@ -84,24 +97,27 @@ enum ClipStopperType { namespace Notify { - void userIsBotChanged(UserData *user); - void userIsContactChanged(UserData *user, bool fromThisApp = false); - void botCommandsChanged(UserData *user); +void userIsBotChanged(UserData *user); +void userIsContactChanged(UserData *user, bool fromThisApp = false); +void botCommandsChanged(UserData *user); - void inlineBotRequesting(bool requesting); +void inlineBotRequesting(bool requesting); +void replyMarkupUpdated(const HistoryItem *item); +void inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop); +bool switchInlineBotButtonReceived(const QString &query); - void migrateUpdated(PeerData *peer); +void migrateUpdated(PeerData *peer); - void clipStopperHidden(ClipStopperType type); +void clipStopperHidden(ClipStopperType type); - void historyItemLayoutChanged(const HistoryItem *item); +void historyItemLayoutChanged(const HistoryItem *item); +void inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout); +void historyMuteUpdated(History *history); - void automaticLoadSettingsChangedGif(); +// handle pending resize() / paint() on history items +void handlePendingHistoryUpdate(); - // handle pending resize() / paint() on history items - void handlePendingHistoryUpdate(); - -}; +} // namespace Notify #define DeclareReadOnlyVar(Type, Name) const Type &Name(); #define DeclareRefVar(Type, Name) DeclareReadOnlyVar(Type, Name) \ @@ -111,115 +127,123 @@ namespace Notify { namespace Sandbox { - bool CheckBetaVersionDir(); - void WorkingDirReady(); +bool CheckBetaVersionDir(); +void WorkingDirReady(); - void start(); - void finish(); +void start(); +void finish(); - uint64 UserTag(); +uint64 UserTag(); - DeclareReadOnlyVar(QString, LangSystemISO); - DeclareReadOnlyVar(int32, LangSystem); - DeclareVar(QByteArray, LastCrashDump); - DeclareVar(ConnectionProxy, PreLaunchProxy); +DeclareReadOnlyVar(QString, LangSystemISO); +DeclareReadOnlyVar(int32, LangSystem); +DeclareVar(QByteArray, LastCrashDump); +DeclareVar(ConnectionProxy, PreLaunchProxy); -} +} // namespace Sandbox namespace Adaptive { - enum Layout { - OneColumnLayout, - NormalLayout, - WideLayout, - }; +enum Layout { + OneColumnLayout, + NormalLayout, + WideLayout, }; +} // namespace Adaptive namespace DebugLogging { - enum Flags { - FileLoaderFlag = 0x00000001, - }; -} +enum Flags { + FileLoaderFlag = 0x00000001, +}; +} // namespace DebugLogging 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; -} + +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; +}; +using Sets = QMap; +using Order = QList; + +} // namespace Stickers namespace Global { - bool started(); - void start(); - void finish(); +bool started(); +void start(); +void finish(); - DeclareReadOnlyVar(uint64, LaunchId); - DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate); +DeclareReadOnlyVar(uint64, LaunchId); +DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate); - DeclareVar(Adaptive::Layout, AdaptiveLayout); - DeclareVar(bool, AdaptiveForWide); +DeclareVar(Adaptive::Layout, AdaptiveLayout); +DeclareVar(bool, AdaptiveForWide); +DeclareVar(bool, DialogsModeEnabled); +DeclareVar(Dialogs::Mode, DialogsMode); - DeclareVar(int32, DebugLoggingFlags); +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); +// 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 QMap HiddenPinnedMessagesMap; +DeclareVar(HiddenPinnedMessagesMap, HiddenPinnedMessages); - typedef OrderedSet PendingItemsMap; - DeclareRefVar(PendingItemsMap, PendingRepaintItems); +typedef OrderedSet PendingItemsMap; +DeclareRefVar(PendingItemsMap, PendingRepaintItems); - DeclareVar(Stickers::Sets, StickerSets); - DeclareVar(Stickers::Order, StickerSetsOrder); - DeclareVar(uint64, LastStickersUpdate); +DeclareVar(Stickers::Sets, StickerSets); +DeclareVar(Stickers::Order, StickerSetsOrder); +DeclareVar(uint64, LastStickersUpdate); - DeclareVar(MTP::DcOptions, DcOptions); +DeclareVar(MTP::DcOptions, DcOptions); - typedef QMap CircleMasksMap; - DeclareRefVar(CircleMasksMap, CircleMasks); +typedef QMap CircleMasksMap; +DeclareRefVar(CircleMasksMap, CircleMasks); -}; +} // namespace Global 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); - } + +inline bool OneColumn() { + return Global::AdaptiveLayout() == OneColumnLayout; +} +inline bool Normal() { + return Global::AdaptiveLayout() == NormalLayout; +} +inline bool Wide() { + return Global::AdaptiveForWide() && (Global::AdaptiveLayout() == WideLayout); } +} // namespace Adaptive + namespace DebugLogging { - inline bool FileLoader() { - return (Global::DebugLoggingFlags() | FileLoaderFlag) != 0; - } + +inline bool FileLoader() { + return (Global::DebugLoggingFlags() | FileLoaderFlag) != 0; } + +} // namespace DebugLogging diff --git a/Telegram/SourceFiles/fileuploader.cpp b/Telegram/SourceFiles/fileuploader.cpp index e16bdc98d..1487168ab 100644 --- a/Telegram/SourceFiles/fileuploader.cpp +++ b/Telegram/SourceFiles/fileuploader.cpp @@ -142,14 +142,14 @@ void FileUploader::sendNext() { if (requestsSent.isEmpty() && docRequestsSent.isEmpty()) { bool silent = i->file && i->file->to.silent; if (i->type() == PreparePhoto) { - emit photoReady(uploading, silent, 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))); + 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, 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_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, silent, doc); } @@ -187,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::uplDcId(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::uplDcId(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); @@ -200,7 +200,7 @@ void FileUploader::sendNext() { } 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::uplDcId(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(); @@ -307,7 +307,7 @@ void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) { } bool FileUploader::partFailed(const RPCError &error, mtpRequestId requestId) { - if (mtpIsFlood(error)) return false; + if (MTP::isDefaultHandledError(error)) return false; if (requestsSent.constFind(requestId) != requestsSent.cend() || docRequestsSent.constFind(requestId) != docRequestsSent.cend()) { // failed to upload current file currentFailed(); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 1478fe97c..930d7c65e 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -19,271 +19,96 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "style.h" -#include "lang.h" +#include "history.h" +#include "core/click_handler_types.h" +#include "dialogs/dialogs_indexed_list.h" +#include "ui/style.h" +#include "lang.h" #include "mainwidget.h" #include "application.h" #include "fileuploader.h" -#include "window.h" -#include "gui/filedialog.h" - +#include "mainwindow.h" +#include "ui/filedialog.h" #include "boxes/addcontactbox.h" #include "boxes/confirmbox.h" - #include "audio.h" #include "localstorage.h" +#include "apiwrap.h" +#include "window/top_bar_widget.h" +#include "playerwidget.h" namespace { - TextParseOptions _historySrvOptions = { - TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // lang-dependent - }; - TextParseOptions _webpageTitleOptions = { - TextParseMultiline | TextParseRichText, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir - }; - TextParseOptions _webpageDescriptionOptions = { - TextParseLinks | TextParseMultiline | TextParseRichText, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir - }; - TextParseOptions _twitterDescriptionOptions = { - TextParseLinks | TextParseMentions | TextTwitterMentions | TextParseHashtags | TextTwitterHashtags | TextParseMultiline | TextParseRichText, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir - }; - TextParseOptions _instagramDescriptionOptions = { - TextParseLinks | TextParseMentions | TextInstagramMentions | TextParseHashtags | TextInstagramHashtags | TextParseMultiline | TextParseRichText, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir - }; - inline void _initTextOptions() { - _historySrvOptions.dir = _textNameOptions.dir = _textDlgOptions.dir = cLangDir(); - _textDlgOptions.maxw = st::dlgMaxWidth * 2; - _webpageTitleOptions.maxw = st::msgMaxWidth - st::msgPadding.left() - st::msgPadding.right() - st::webPageLeft; - _webpageTitleOptions.maxh = st::webPageTitleFont->height * 2; - _webpageDescriptionOptions.maxw = st::msgMaxWidth - st::msgPadding.left() - st::msgPadding.right() - st::webPageLeft; - _webpageDescriptionOptions.maxh = st::webPageDescriptionFont->height * 3; - } +TextParseOptions _historySrvOptions = { + TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // lang-dependent +}; +TextParseOptions _webpageTitleOptions = { + TextParseMultiline | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; +TextParseOptions _webpageDescriptionOptions = { + TextParseLinks | TextParseMultiline | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; +TextParseOptions _twitterDescriptionOptions = { + TextParseLinks | TextParseMentions | TextTwitterMentions | TextParseHashtags | TextTwitterHashtags | TextParseMultiline | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; +TextParseOptions _instagramDescriptionOptions = { + TextParseLinks | TextParseMentions | TextInstagramMentions | TextParseHashtags | TextInstagramHashtags | TextParseMultiline | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; - inline const TextParseOptions &itemTextOptions(HistoryItem *item) { - return itemTextOptions(item->history(), item->author()); - } - inline const TextParseOptions &itemTextNoMonoOptions(const HistoryItem *item) { - return itemTextNoMonoOptions(item->history(), item->author()); - } +inline void _initTextOptions() { + _historySrvOptions.dir = _textNameOptions.dir = _textDlgOptions.dir = cLangDir(); + _textDlgOptions.maxw = st::dlgMaxWidth * 2; + _webpageTitleOptions.maxw = st::msgMaxWidth - st::msgPadding.left() - st::msgPadding.right() - st::webPageLeft; + _webpageTitleOptions.maxh = st::webPageTitleFont->height * 2; + _webpageDescriptionOptions.maxw = st::msgMaxWidth - st::msgPadding.left() - st::msgPadding.right() - st::webPageLeft; + _webpageDescriptionOptions.maxh = st::webPageDescriptionFont->height * 3; } +inline const TextParseOptions &itemTextOptions(HistoryItem *item) { + return itemTextOptions(item->history(), item->author()); +} +inline const TextParseOptions &itemTextNoMonoOptions(const HistoryItem *item) { + return itemTextNoMonoOptions(item->history(), item->author()); +} + +bool needReSetInlineResultDocument(const MTPMessageMedia &media, DocumentData *existing) { + if (media.type() == mtpc_messageMediaDocument) { + if (DocumentData *document = App::feedDocument(media.c_messageMediaDocument().vdocument)) { + if (document == existing) { + return false; + } else { + document->collectLocalData(existing); + } + } + } + return true; +} + +} // namespace + void historyInit() { _initTextOptions(); } -void DialogRow::paint(Painter &p, int32 w, bool act, bool sel, bool onlyBackground) const { - QRect fullRect(0, 0, w, st::dlgHeight); - p.fillRect(fullRect, (act ? st::dlgActiveBG : (sel ? st::dlgHoverBG : st::dlgBG))->b); - if (onlyBackground) return; - - 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; - QRect rectForName(nameleft, st::dlgPaddingVer + st::dlgNameTop, namewidth, st::msgNameFont->height); - - // draw chat icon - if (history->peer->isChat() || history->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 (history->peer->isChannel()) { - p.drawPixmap(QPoint(rectForName.left() + st::dlgChannelImgPos.x(), rectForName.top() + st::dlgChannelImgPos.y()), App::sprite(), (act ? st::dlgActiveChannelImg : st::dlgChannelImg)); - rectForName.setLeft(rectForName.left() + st::dlgImgSkip); - } - - HistoryItem *last = history->lastMsg; - if (!last) { - p.setFont(st::dlgHistFont->f); - p.setPen((act ? st::dlgActiveColor : st::dlgSystemColor)->p); - if (history->typing.isEmpty() && history->sendActions.isEmpty()) { - p.drawText(nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgFont->ascent + st::dlgSep, lang(lng_empty_history)); - } else { - history->typingText.drawElided(p, nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgSep, namewidth); - } - } else { - // draw date - QDateTime now(QDateTime::currentDateTime()), lastTime(last->date); - QDate nowDate(now.date()), lastDate(lastTime.date()); - QString dt; - if (lastDate == nowDate) { - dt = lastTime.toString(cTimeFormat()); - } else if (lastDate.year() == nowDate.year() && lastDate.weekNumber() == nowDate.weekNumber()) { - dt = langDayOfWeek(lastDate); - } else { - dt = lastDate.toString(qsl("d.MM.yy")); - } - int32 dtWidth = st::dlgDateFont->width(dt); - rectForName.setWidth(rectForName.width() - dtWidth - st::dlgDateSkip); - p.setFont(st::dlgDateFont->f); - p.setPen((act ? st::dlgActiveDateColor : st::dlgDateColor)->p); - p.drawText(rectForName.left() + rectForName.width() + st::dlgDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, dt); - - // draw check - if (last->needCheck()) { - const style::sprite *check; - if (last->id > 0) { - if (last->unread()) { - check = act ? &st::dlgActiveCheckImg : &st::dlgCheckImg; - } else { - check = act ? &st::dlgActiveDblCheckImg: &st::dlgDblCheckImg; - } - } else { - check = act ? &st::dlgActiveSendImg : &st::dlgSendImg; - } - rectForName.setWidth(rectForName.width() - check->pxWidth() - st::dlgCheckSkip); - p.drawPixmap(QPoint(rectForName.left() + rectForName.width() + st::dlgCheckLeft, rectForName.top() + st::dlgCheckTop), App::sprite(), *check); - } - - // draw unread - int32 lastWidth = namewidth, unread = history->unreadCount; - if (history->peer->migrateFrom()) { - if (History *h = App::historyLoaded(history->peer->migrateFrom()->id)) { - unread += h->unreadCount; - } - } - if (unread) { - QString unreadStr = QString::number(unread); - int32 unreadWidth = st::dlgUnreadFont->width(unreadStr); - int32 unreadRectWidth = unreadWidth + 2 * st::dlgUnreadPaddingHor; - int32 unreadRectHeight = st::dlgUnreadFont->height + 2 * st::dlgUnreadPaddingVer; - int32 unreadRectLeft = w - st::dlgPaddingHor - unreadRectWidth; - int32 unreadRectTop = st::dlgHeight - st::dlgPaddingVer - unreadRectHeight; - lastWidth -= unreadRectWidth + st::dlgUnreadPaddingHor; - 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); - p.setPen((act ? st::dlgActiveUnreadColor : st::dlgUnreadColor)->p); - p.drawText(unreadRectLeft + st::dlgUnreadPaddingHor, unreadRectTop + st::dlgUnreadPaddingVer + st::dlgUnreadFont->ascent, unreadStr); - } - if (history->typing.isEmpty() && history->sendActions.isEmpty()) { - last->drawInDialog(p, QRect(nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgSep, lastWidth, st::dlgFont->height), act, history->textCachedFor, history->lastItemTextCache); - } else { - p.setPen((act ? st::dlgActiveColor : st::dlgSystemColor)->p); - history->typingText.drawElided(p, nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgSep, lastWidth); - } - } - - if (history->peer->isUser() && history->peer->isVerified()) { - rectForName.setWidth(rectForName.width() - st::verifiedCheck.pxWidth() - st::verifiedCheckPos.x()); - p.drawSprite(rectForName.topLeft() + QPoint(qMin(history->peer->dialogName().maxWidth(), rectForName.width()), 0) + st::verifiedCheckPos, (act ? st::verifiedCheckInv : st::verifiedCheck)); - } - - p.setPen((act ? st::dlgActiveColor : st::dlgNameColor)->p); - history->peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); -} - -void FakeDialogRow::paint(Painter &p, int32 w, bool act, bool sel, bool onlyBackground) const { - QRect fullRect(0, 0, w, st::dlgHeight); - p.fillRect(fullRect, (act ? st::dlgActiveBG : (sel ? st::dlgHoverBG : st::dlgBG))->b); - if (onlyBackground) return; - - History *history = _item->history(); - 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; - QRect rectForName(nameleft, st::dlgPaddingVer + st::dlgNameTop, namewidth, st::msgNameFont->height); - - // draw chat icon - if (history->peer->isChat() || history->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 (history->peer->isChannel()) { - p.drawPixmap(QPoint(rectForName.left() + st::dlgChannelImgPos.x(), rectForName.top() + st::dlgChannelImgPos.y()), App::sprite(), (act ? st::dlgActiveChannelImg : st::dlgChannelImg)); - rectForName.setLeft(rectForName.left() + st::dlgImgSkip); - } - - // draw date - QDateTime now(QDateTime::currentDateTime()), lastTime(_item->date); - QDate nowDate(now.date()), lastDate(lastTime.date()); - QString dt; - if (lastDate == nowDate) { - dt = lastTime.toString(cTimeFormat()); - } else if (lastDate.year() == nowDate.year() && lastDate.weekNumber() == nowDate.weekNumber()) { - dt = langDayOfWeek(lastDate); - } else { - dt = lastDate.toString(qsl("d.MM.yy")); - } - int32 dtWidth = st::dlgDateFont->width(dt); - rectForName.setWidth(rectForName.width() - dtWidth - st::dlgDateSkip); - p.setFont(st::dlgDateFont->f); - p.setPen((act ? st::dlgActiveDateColor : st::dlgDateColor)->p); - p.drawText(rectForName.left() + rectForName.width() + st::dlgDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, dt); - - // draw check - if (_item->needCheck()) { - const style::sprite *check; - if (_item->id > 0) { - if (_item->unread()) { - check = act ? &st::dlgActiveCheckImg : &st::dlgCheckImg; - } else { - check = act ? &st::dlgActiveDblCheckImg : &st::dlgDblCheckImg; - } - } else { - check = act ? &st::dlgActiveSendImg : &st::dlgSendImg; - } - rectForName.setWidth(rectForName.width() - check->pxWidth() - st::dlgCheckSkip); - p.drawPixmap(QPoint(rectForName.left() + rectForName.width() + st::dlgCheckLeft, rectForName.top() + st::dlgCheckTop), App::sprite(), *check); - } - - // draw unread - int32 lastWidth = namewidth; - _item->drawInDialog(p, QRect(nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgSep, lastWidth, st::dlgFont->height), act, _cacheFor, _cache); - - if (history->peer->isUser() && history->peer->isVerified()) { - rectForName.setWidth(rectForName.width() - st::verifiedCheck.pxWidth() - st::verifiedCheckPos.x()); - p.drawSprite(rectForName.topLeft() + QPoint(qMin(history->peer->dialogName().maxWidth(), rectForName.width()), 0) + st::verifiedCheckPos, (act ? st::verifiedCheckInv : st::verifiedCheck)); - } - - p.setPen((act ? st::dlgActiveColor : st::dlgNameColor)->p); - history->peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); -} - -History::History(const PeerId &peerId) : width(0), height(0) -, unreadCount(0) -, inboxReadBefore(1) -, outboxReadBefore(1) -, showFrom(nullptr) -, unreadBar(nullptr) -, peer(App::peer(peerId)) -, oldLoaded(false) -, newLoaded(true) -, lastMsg(0) -, msgDraft(0) -, editDraft(0) -, showAtMsgId(ShowAtUnreadMsgId) -, scrollTopItem(nullptr) -, scrollTopOffset(0) -, mute(isNotifyMuted(peer->notify)) -, lastKeyboardInited(false) -, lastKeyboardUsed(false) -, lastKeyboardId(0) -, lastKeyboardHiddenId(0) -, lastKeyboardFrom(0) -, sendRequestId(0) -, textCachedFor(0) -, lastItemTextCache(st::dlgRichMinWidth) -, typingText(st::dlgRichMinWidth) -, _sortKeyInChatList(0) { +History::History(const PeerId &peerId) +: peer(App::peer(peerId)) +, _mute(isNotifyMuted(peer->notify)) { if (peer->isChannel() || (peer->isUser() && peer->asUser()->botInfo)) { outboxReadBefore = INT_MAX; } @@ -377,11 +202,13 @@ bool History::updateTyping(uint64 ms, 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) { @@ -561,7 +388,7 @@ void ChannelHistory::getRangeDifferenceNext(int32 pts) { void ChannelHistory::addNewGroup(const MTPMessageGroup &group) { if (group.type() != mtpc_messageGroup) return; - const MTPDmessageGroup &d(group.c_messageGroup()); + const auto &d(group.c_messageGroup()); if (onlyImportant()) { _otherNewLoaded = false; @@ -575,14 +402,8 @@ void ChannelHistory::addNewGroup(const MTPMessageGroup &group) { if (onlyImportant()) { if (newLoaded) { - HistoryBlock *block = blocks.isEmpty() ? pushBackNewBlock() : blocks.back(); - HistoryItem *prev = block->items.isEmpty() ? nullptr : block->items.back(); - - prev = addMessageGroupAfterPrevToBlock(d, prev, block); - if (block->items.isEmpty()) { - blocks.pop_back(); - delete block; - } + t_assert(!isBuildingFrontBlock()); + addMessageGroup(d); } } else { setNotLoadedAtBottom(); @@ -639,14 +460,12 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) { } } - // adding new item to new block - HistoryBlock *block = pushFrontNewBlock(); + startBuildingFrontBlock(); _joinedMessage = HistoryJoined::create(this, inviteDate, inviter, flags); - addItemToBlock(_joinedMessage, block); + addItemToBlock(_joinedMessage); - t_assert(blocks.size() > 1); - blocks.at(1)->items.front()->previousItemChanged(); + finishBuildingFrontBlock(); return _joinedMessage; } @@ -755,15 +574,15 @@ HistoryItem *ChannelHistory::addNewToBlocks(const MTPMessage &msg, NewMessageTyp } if (!isImportant && onlyImportant()) { - HistoryItem *item = addToHistory(msg), *prev = isEmpty() ? nullptr : blocks.back()->items.back(); + 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); + }); - if (prev && prev->type() == HistoryItemGroup) { - static_cast(prev)->uniteWith(item); - } else { - QDateTime date = prev ? prev->date : item->date; - HistoryBlock *block = prev ? prev->block() : pushBackNewBlock(); - addItemToBlock(HistoryGroup::create(this, item, date), block); - } return item; } @@ -819,22 +638,15 @@ void ChannelHistory::switchMode() { clear(true); + t_assert(!isBuildingFrontBlock()); + newLoaded = _otherNewLoaded; oldLoaded = _otherOldLoaded; if (int count = _otherList.size()) { - blocks.reserve(qCeil(count / float64(MessagesPerPage))); - - for (int i = 0; i < count;) { - HistoryBlock *block = pushBackNewBlock(); - - int willAddToBlock = qMin(int(MessagesPerPage), count - i); - block->items.reserve(willAddToBlock); - for (int till = i + willAddToBlock; i < till; ++i) { - t_assert(_otherList.at(i)->detached()); - addItemToBlock(_otherList.at(i), block); - } - - t_assert(!block->items.isEmpty()); + blocks.reserve((count / MessagesPerPage) + 1); + for (int i = 0; i < count; ++i) { + t_assert(_otherList.at(i)->detached()); + addItemToBlock(_otherList.at(i)); } } @@ -1017,107 +829,6 @@ ChannelHistory::~ChannelHistory() { 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(); - if (App::main()) { - emit App::main()->dialogRowReplaced(row, replacedBy); - } - - if (row == current) { - current = row->next; - } - for (DialogRow *change = row->next; change != end; change = change->next) { - change->pos--; - } - end->pos--; - remove(row); - delete row; - --count; - rowByPeer.erase(i); - - return true; -} - -void DialogsIndexed::peerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { - if (sortMode == DialogsSortByName) { - DialogRow *mainRow = list.adjustByName(peer); - if (!mainRow) return; - - History *history = mainRow->history; - - PeerData::NameFirstChars toRemove = oldChars, toAdd; - for (PeerData::NameFirstChars::const_iterator i = peer->chars.cbegin(), e = peer->chars.cend(); i != e; ++i) { - PeerData::NameFirstChars::iterator j = toRemove.find(*i); - if (j == toRemove.cend()) { - toAdd.insert(*i); - } else { - toRemove.erase(j); - DialogsIndex::iterator k = index.find(*i); - if (k != index.cend()) { - k.value()->adjustByName(peer); - } - } - } - for (PeerData::NameFirstChars::const_iterator i = toRemove.cbegin(), e = toRemove.cend(); i != e; ++i) { - DialogsIndex::iterator j = index.find(*i); - if (j != index.cend()) { - j.value()->del(peer->id, mainRow); - } - } - if (!toAdd.isEmpty()) { - for (PeerData::NameFirstChars::const_iterator i = toAdd.cbegin(), e = toAdd.cend(); i != e; ++i) { - DialogsIndex::iterator j = index.find(*i); - if (j == index.cend()) { - j = index.insert(*i, new DialogsList(sortMode)); - } - j.value()->addByName(history); - } - } - } else { - DialogsList::RowByPeer::const_iterator i = list.rowByPeer.find(peer->id); - if (i == list.rowByPeer.cend()) return; - - DialogRow *mainRow = i.value(); - History *history = mainRow->history; - - PeerData::NameFirstChars toRemove = oldChars, toAdd; - for (PeerData::NameFirstChars::const_iterator i = peer->chars.cbegin(), e = peer->chars.cend(); i != e; ++i) { - PeerData::NameFirstChars::iterator j = toRemove.find(*i); - if (j == toRemove.cend()) { - toAdd.insert(*i); - } else { - toRemove.erase(j); - } - } - for (PeerData::NameFirstChars::const_iterator i = toRemove.cbegin(), e = toRemove.cend(); i != e; ++i) { - if (sortMode == DialogsSortByDate) history->removeChatListEntryByLetter(*i); - DialogsIndex::iterator j = index.find(*i); - if (j != index.cend()) { - j.value()->del(peer->id, mainRow); - } - } - for (PeerData::NameFirstChars::const_iterator i = toAdd.cbegin(), e = toAdd.cend(); i != e; ++i) { - DialogsIndex::iterator j = index.find(*i); - if (j == index.cend()) { - j = index.insert(*i, new DialogsList(sortMode)); - } - DialogRow *row = j.value()->addToEnd(history); - if (sortMode == DialogsSortByDate) history->addChatListEntryByLetter(*i, row); - } - } -} - -void DialogsIndexed::clear() { - for (DialogsIndex::iterator i = index.begin(), e = index.end(); i != e; ++i) { - delete i.value(); - } - index.clear(); - list.clear(); -} - History *Histories::find(const PeerId &peerId) { Map::const_iterator i = map.constFind(peerId); return (i == map.cend()) ? 0 : i.value(); @@ -1138,6 +849,8 @@ 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(); @@ -1202,11 +915,40 @@ void Histories::remove(const PeerId &peer) { } } +namespace { + +void checkForSwitchInlineButton(HistoryItem *item) { + if (item->out() || !item->hasSwitchInlineButton()) { + return; + } + if (UserData *user = item->history()->peer->asUser()) { + if (!user->botInfo || !user->botInfo->inlineReturnPeerId) { + return; + } + if (auto markup = item->Get()) { + for_const (const auto &row, markup->rows) { + for_const (const auto &button, row) { + if (button.type == HistoryMessageReplyMarkup::Button::SwitchInline) { + Notify::switchInlineBotButtonReceived(QString::fromUtf8(button.data)); + return; + } + } + } + } + } +} + +} // namespace + HistoryItem *Histories::addNewMessage(const MTPMessage &msg, NewMessageType type) { PeerId peer = peerFromMessage(msg); - if (!peer) return 0; + if (!peer) return nullptr; - return findOrInsert(peer, 0, 0)->addNewMessage(msg, type); + HistoryItem *result = findOrInsert(peer, 0, 0)->addNewMessage(msg, type); + if (result && type == NewMessageUnread) { + checkForSwitchInlineButton(result); + } + return result; } HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) { @@ -1238,7 +980,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, break; case mtpc_message: { - const MTPDmessage m(msg.c_message()); + const auto &m(msg.c_message()); int badMedia = 0; // 1 - unsupported, 2 - empty if (m.has_media()) switch (m.vmedia.type()) { case mtpc_messageMediaEmpty: @@ -1288,26 +1030,23 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, 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 = HistoryService::create(this, m.vid.v, date(m.vdate), lang(lng_message_empty), m.vflags.v, nullptr, 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 { result = HistoryMessage::create(this, m); - if (m.has_reply_markup()) { - App::feedReplyMarkup(channelId(), msgId, m.vreply_markup); - } } } break; case mtpc_messageService: { - const MTPDmessageService &d(msg.c_messageService()); + const auto &d(msg.c_messageService()); result = HistoryService::create(this, d); if (applyServiceAction) { - const MTPmessageAction &action(d.vaction); + const auto &action(d.vaction); switch (d.vaction.type()) { case mtpc_messageActionChatAddUser: { - const MTPDmessageActionChatAddUser &d(action.c_messageActionChatAddUser()); + const auto &d(action.c_messageActionChatAddUser()); if (peer->isMegagroup()) { - const QVector &v(d.vusers.c_vector().v); + const auto &v(d.vusers.c_vector().v); for (int32 i = 0, l = v.size(); i < l; ++i) { if (UserData *user = App::userLoaded(peerFromUser(v.at(i)))) { if (peer->asChannel()->mgInfo->lastParticipants.indexOf(user) < 0) { @@ -1326,7 +1065,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, } break; case mtpc_messageActionChatJoinedByLink: { - const MTPDmessageActionChatJoinedByLink &d(action.c_messageActionChatJoinedByLink()); + const auto &d(action.c_messageActionChatJoinedByLink()); if (peer->isMegagroup()) { if (result->from()->isUser()) { if (peer->asChannel()->mgInfo->lastParticipants.indexOf(result->from()->asUser()) < 0) { @@ -1348,7 +1087,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, } break; case mtpc_messageActionChatDeleteUser: { - const MTPDmessageActionChatDeleteUser &d(action.c_messageActionChatDeleteUser()); + const auto &d(action.c_messageActionChatDeleteUser()); PeerId uid = peerFromUser(d.vuser_id); if (lastKeyboardFrom == uid) { clearLastKeyboard(); @@ -1381,13 +1120,13 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, } break; case mtpc_messageActionChatEditPhoto: { - const MTPDmessageActionChatEditPhoto &d(action.c_messageActionChatEditPhoto()); + const auto &d(action.c_messageActionChatEditPhoto()); if (d.vphoto.type() == mtpc_photo) { - const QVector &sizes(d.vphoto.c_photo().vsizes.c_vector().v); + const auto &sizes(d.vphoto.c_photo().vsizes.c_vector().v); if (!sizes.isEmpty()) { PhotoData *photo = App::feedPhoto(d.vphoto.c_photo()); if (photo) photo->peer = peer; - const MTPPhotoSize &smallSize(sizes.front()), &bigSize(sizes.back()); + const auto &smallSize(sizes.front()), &bigSize(sizes.back()); const MTPFileLocation *smallLoc = 0, *bigLoc = 0; switch (smallSize.type()) { case mtpc_photoSize: smallLoc = &smallSize.c_photoSize().vlocation; break; @@ -1410,7 +1149,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, } break; case mtpc_messageActionChatEditTitle: { - const MTPDmessageActionChatEditTitle &d(action.c_messageActionChatEditTitle()); + const auto &d(action.c_messageActionChatEditTitle()); ChatData *chat = peer->asChat(); if (chat) chat->updateName(qs(d.vtitle), QString(), QString()); } break; @@ -1418,12 +1157,12 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, case mtpc_messageActionChatMigrateTo: { peer->asChat()->flags |= MTPDchat::Flag::f_deactivated; - //const MTPDmessageActionChatMigrateTo &d(action.c_messageActionChatMigrateTo()); + //const auto &d(action.c_messageActionChatMigrateTo()); //PeerData *channel = App::channelLoaded(d.vchannel_id.v); } break; case mtpc_messageActionChannelMigrateFrom: { - //const MTPDmessageActionChannelMigrateFrom &d(action.c_messageActionChannelMigrateFrom()); + //const auto &d(action.c_messageActionChannelMigrateFrom()); //PeerData *chat = App::chatLoaded(d.vchat_id.v); } break; @@ -1449,16 +1188,16 @@ HistoryItem *History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QD return HistoryMessage::create(this, id, flags, date, from, msg); } -HistoryItem *History::createItemDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption) { - return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, doc, caption); +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(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption) { - return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, photo, caption); +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, MTPDmessage::Flags flags, HistoryMedia *media, bool newMsg) { - return addNewItem(HistoryService::create(this, msgId, date, text, flags, media), 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) { @@ -1496,12 +1235,12 @@ HistoryItem *History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateT return addNewItem(createItemForwarded(id, flags, date, from, item), true); } -HistoryItem *History::addNewDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption) { - return addNewItem(createItemDocument(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, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption) { - return addNewItem(createItemPhoto(id, flags, viaBotId, replyTo, date, from, photo, caption), true); +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) { @@ -1548,16 +1287,10 @@ void History::eraseFromOverview(MediaOverviewType type, MsgId msgId) { } HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) { - t_assert(adding != nullptr); - t_assert(adding->detached()); + t_assert(!isBuildingFrontBlock()); + addItemToBlock(adding); - HistoryBlock *block = blocks.isEmpty() ? pushBackNewBlock() : blocks.back(); - - adding->attachToBlock(block, block->items.size()); - block->items.push_back(adding); - adding->previousItemChanged(); setLastMessage(adding); - if (newMsg) { newItemAdded(adding); } @@ -1589,8 +1322,8 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) { } } } - if (adding->hasReplyMarkup()) { - MTPDreplyKeyboardMarkup::Flags markupFlags = App::replyMarkup(channelId(), adding->id).flags; + if (adding->definesReplyKeyboard()) { + MTPDreplyKeyboardMarkup::Flags markupFlags = adding->replyKeyboardFlags(); if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || adding->mentionsMe()) { OrderedSet *markupSenders = 0; if (peer->isChat()) { @@ -1669,19 +1402,50 @@ void History::newItemAdded(HistoryItem *item) { } } -HistoryItem *History::addItemToBlock(HistoryItem *item, HistoryBlock *block) { +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); item->previousItemChanged(); - return item; -} -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 addItemToBlock(HistoryGroup::create(this, group, prev ? prev->date : date(group.vdate)), block); } void History::addOlderSlice(const QVector &slice, const QVector *collapsed) { @@ -1698,10 +1462,8 @@ void History::addOlderSlice(const QVector &slice, const QVectorconstData() : 0, *groupsIt = groupsBegin, *groupsEnd = (isChannel() && collapsed) ? (groupsBegin + collapsed->size()) : 0; - HistoryItem *prev = nullptr; - HistoryBlock *block = pushFrontNewBlock(); + startBuildingFrontBlock(slice.size() + (collapsed ? collapsed->size() : 0)); - block->items.reserve(slice.size() + (collapsed ? collapsed->size() : 0)); for (auto i = slice.cend(), e = slice.cbegin(); i != e;) { --i; HistoryItem *adding = createItem(*i, false, true); @@ -1709,26 +1471,24 @@ void History::addOlderSlice(const QVector &slice, const QVectortype() != mtpc_messageGroup) continue; - const MTPDmessageGroup &group(groupsIt->c_messageGroup()); + const auto &group(groupsIt->c_messageGroup()); if (group.vmin_id.v >= adding->id) break; - prev = addMessageGroupAfterPrevToBlock(group, prev, block); + addMessageGroup(group); } - prev = addItemToBlock(adding, block); + addItemToBlock(adding); } for (; groupsIt != groupsEnd; ++groupsIt) { if (groupsIt->type() != mtpc_messageGroup) continue; - const MTPDmessageGroup &group(groupsIt->c_messageGroup()); + const auto &group(groupsIt->c_messageGroup()); - prev = addMessageGroupAfterPrevToBlock(group, prev, block); + addMessageGroup(group); } - if (block->items.isEmpty()) { - blocks.pop_front(); - delete block; - block = nullptr; - + HistoryBlock *block = finishBuildingFrontBlock(); + if (!block) { + // If no items were added it means we've loaded everything old. oldLoaded = true; } else if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors / lastParticipants bool channel = isChannel(); @@ -1759,8 +1519,8 @@ void History::addOlderSlice(const QVector &slice, const QVectorauthor()->id) { if (markupSenders) { // chats with bots - if (!lastKeyboardInited && item->hasReplyMarkup() && !item->out()) { - MTPDreplyKeyboardMarkup::Flags markupFlags = App::replyMarkup(channelId(), item->id).flags; + 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) { @@ -1786,8 +1546,8 @@ void History::addOlderSlice(const QVector &slice, const QVectorhasReplyMarkup() && !item->out()) { // conversations with bots - MTPDreplyKeyboardMarkup::Flags markupFlags = App::replyMarkup(channelId(), item->id).flags; + } 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(); @@ -1806,33 +1566,11 @@ void History::addOlderSlice(const QVector &slice, const QVector 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 = nullptr; - } - } - if (isChannel()) { asChannelHistory()->checkJoinedMessage(); asChannelHistory()->checkMaxReadMessageDate(); } - if (newLoaded && !lastMsg) setLastMessage(lastImportantMessage()); + checkLastMsg(); } void History::addNewerSlice(const QVector &slice, const QVector *collapsed) { @@ -1840,17 +1578,16 @@ 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() ? nullptr : blocks.back()->items.back(); - - HistoryBlock *block = pushBackNewBlock(); - - block->items.reserve(slice.size() + (collapsed ? collapsed->size() : 0)); + bool atLeastOneAdded = false; for (auto i = slice.cend(), e = slice.cbegin(); i != e;) { --i; HistoryItem *adding = createItem(*i, false, true); @@ -1858,28 +1595,25 @@ void History::addNewerSlice(const QVector &slice, const QVectortype() != mtpc_messageGroup) continue; - const MTPDmessageGroup &group(groupsIt->c_messageGroup()); + const auto &group(groupsIt->c_messageGroup()); if (group.vmin_id.v >= adding->id) break; - prev = addMessageGroupAfterPrevToBlock(group, prev, block); + addMessageGroup(group); } - prev = addItemToBlock(adding, block); + addItemToBlock(adding); + atLeastOneAdded = true; } for (; groupsIt != groupsEnd; ++groupsIt) { if (groupsIt->type() != mtpc_messageGroup) continue; - const MTPDmessageGroup &group(groupsIt->c_messageGroup()); + const auto &group(groupsIt->c_messageGroup()); - prev = addMessageGroupAfterPrevToBlock(group, prev, block); + addMessageGroup(group); } - if (block->items.isEmpty()) { + if (!atLeastOneAdded) { newLoaded = true; setLastMessage(lastImportantMessage()); - - blocks.pop_back(); - delete block; - block = nullptr; } } @@ -1904,6 +1638,17 @@ void History::addNewerSlice(const QVector &slice, const QVectorcheckJoinedMessage(); + checkLastMsg(); +} + +void History::checkLastMsg() { + if (lastMsg) { + if (!newLoaded && !lastMsg->detached() && (!isChannel() || asChannelHistory()->onlyImportant())) { + newLoaded = true; + } + } else if (newLoaded) { + setLastMessage(lastImportantMessage()); + } } int History::countUnread(MsgId upTo) { @@ -1942,7 +1687,7 @@ void History::updateShowFrom() { MsgId History::inboxRead(MsgId upTo) { if (upTo < 0) return upTo; - if (unreadCount) { + if (unreadCount()) { if (upTo && loadedAtBottom()) App::main()->historyToDown(this); setUnreadCount(upTo ? countUnread(upTo) : 0); } @@ -1998,7 +1743,7 @@ HistoryItem *History::lastImportantMessage() const { } void History::setUnreadCount(int newUnreadCount, bool psUpdate) { - if (unreadCount != newUnreadCount) { + if (_unreadCount != newUnreadCount) { if (newUnreadCount == 1) { if (loadedAtBottom()) showFrom = lastImportantMessage(); inboxReadBefore = qMax(inboxReadBefore, msgIdForRead()); @@ -2006,18 +1751,18 @@ void History::setUnreadCount(int newUnreadCount, bool psUpdate) { showFrom = nullptr; inboxReadBefore = qMax(inboxReadBefore, msgIdForRead() + 1); } - if (inChatList()) { - App::histories().unreadIncrement(newUnreadCount - unreadCount, mute); - if (psUpdate && (!mute || cIncludeMuted()) && App::wnd()) { + if (inChatList(Dialogs::Mode::All)) { + App::histories().unreadIncrement(newUnreadCount - _unreadCount, mute()); + if (psUpdate && (!mute() || cIncludeMuted()) && App::wnd()) { App::wnd()->updateCounter(); } } - unreadCount = newUnreadCount; + _unreadCount = newUnreadCount; if (unreadBar) { - int32 count = unreadCount; + int32 count = _unreadCount; if (peer->migrateTo()) { if (History *h = App::historyLoaded(peer->migrateTo()->id)) { - count += h->unreadCount; + count += h->unreadCount(); } } if (count > 0) { @@ -2030,11 +1775,14 @@ void History::setUnreadCount(int newUnreadCount, bool psUpdate) { } void History::setMute(bool newMute) { - if (mute != newMute) { - mute = newMute; - if (inChatList() && unreadCount) { - App::histories().unreadMuteChanged(unreadCount, newMute); - if (App::wnd()) App::wnd()->updateCounter(); + if (_mute != newMute) { + _mute = newMute; + if (inChatList(Dialogs::Mode::All)) { + if (_unreadCount) { + App::histories().unreadMuteChanged(_unreadCount, newMute); + if (App::wnd()) App::wnd()->updateCounter(); + } + Notify::historyMuteUpdated(this); } updateChatListEntry(); } @@ -2140,12 +1888,12 @@ void History::getNextScrollTopItem(HistoryBlock *block, int32 i) { } void History::addUnreadBar() { - if (unreadBar || !showFrom || showFrom->detached() || !unreadCount) return; + if (unreadBar || !showFrom || showFrom->detached() || !unreadCount()) return; - int32 count = unreadCount; + int32 count = unreadCount(); if (peer->migrateTo()) { if (History *h = App::historyLoaded(peer->migrateTo()->id)) { - count += h->unreadCount; + count += h->unreadCount(); } } showFrom->setUnreadBarCount(count); @@ -2179,21 +1927,70 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, return newItem; } -HistoryBlock *History::pushBackNewBlock() { - HistoryBlock *result = new HistoryBlock(this); - result->setIndexInHistory(blocks.size()); - blocks.push_back(result); - return result; +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)); + } } -HistoryBlock *History::pushFrontNewBlock() { - HistoryBlock *result = new HistoryBlock(this); - result->setIndexInHistory(0); - blocks.push_front(result); - for (int i = 1, l = blocks.size(); i < l; ++i) { - blocks.at(i)->setIndexInHistory(i); +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; + } } - return result; + + _buildingFrontBlock = nullptr; + return block; } void History::clearNotifications() { @@ -2224,12 +2021,12 @@ bool History::isReadyFor(MsgId msgId, MsgId &fixInScrollMsgId, int32 &fixInScrol if (msgId == ShowAtUnreadMsgId) { if (peer->migrateFrom()) { // old group history if (History *h = App::historyLoaded(peer->migrateFrom()->id)) { - if (h->unreadCount) { + if (h->unreadCount()) { return h->isReadyFor(msgId, fixInScrollMsgId, fixInScrollMsgTop); } } } - if (unreadCount) { + if (unreadCount()) { if (!isEmpty()) { return (loadedAtTop() || minMsgId() <= inboxReadBefore) && (loadedAtBottom() || maxMsgId() >= inboxReadBefore); } @@ -2255,7 +2052,7 @@ void History::getReadyFor(MsgId msgId, MsgId &fixInScrollMsgId, int32 &fixInScro } if (msgId == ShowAtUnreadMsgId && peer->migrateFrom()) { if (History *h = App::historyLoaded(peer->migrateFrom()->id)) { - if (h->unreadCount) { + if (h->unreadCount()) { clear(true); h->getReadyFor(msgId, fixInScrollMsgId, fixInScrollMsgTop); return; @@ -2294,12 +2091,12 @@ void History::setLastMessage(HistoryItem *msg) { } void History::setChatsListDate(const QDateTime &date) { - bool updateDialog = (App::main() && (!peer->isChannel() || peer->asChannel()->amIn() || !_chatListLinks.isEmpty())); - if (peer->migrateTo() && _chatListLinks.isEmpty()) { + bool updateDialog = (App::main() && (!peer->isChannel() || peer->asChannel()->amIn() || inChatList(Dialogs::Mode::All))); + if (peer->migrateTo() && !inChatList(Dialogs::Mode::All)) { updateDialog = false; } if (!lastMsgDate.isNull() && lastMsgDate >= date) { - if (!updateDialog || !_chatListLinks.isEmpty()) { + if (!updateDialog || !inChatList(Dialogs::Mode::All)) { return; } } @@ -2345,7 +2142,7 @@ MsgId History::msgIdForRead() const { } int History::resizeGetHeight(int newWidth) { - bool resizeAllItems = (_flags | Flag::f_pending_resize) || (width != newWidth); + bool resizeAllItems = (_flags & Flag::f_pending_resize) || (width != newWidth); if (!resizeAllItems && !hasPendingResizedItems()) { return height; @@ -2435,54 +2232,64 @@ 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); +History::PositionInChatListChange History::adjustByPosInChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed) { + t_assert(indexed != nullptr); + Dialogs::Row *lnk = mainChatListLink(list); + int32 movedFrom = lnk->pos(); + indexed->adjustByPos(chatListLinks(list)); + int32 movedTo = lnk->pos(); + return { movedFrom, movedTo }; } -DialogRow *History::addToChatList(DialogsIndexed &indexed) { - if (!inChatList()) { - _chatListLinks = indexed.addToEnd(this); - if (unreadCount) { - App::histories().unreadIncrement(unreadCount, mute); +int History::posInChatList(Dialogs::Mode list) const { + return mainChatListLink(list)->pos(); +} + +Dialogs::Row *History::addToChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed) { + t_assert(indexed != nullptr); + if (!inChatList(list)) { + chatListLinks(list) = indexed->addToEnd(this); + if (list == Dialogs::Mode::All && unreadCount()) { + App::histories().unreadIncrement(unreadCount(), mute()); if (App::wnd()) App::wnd()->updateCounter(); } } - return mainChatListLink(); + return mainChatListLink(list); } -void History::removeFromChatList(DialogsIndexed &indexed) { - if (inChatList()) { - indexed.del(peer); - _chatListLinks.clear(); - if (unreadCount) { - App::histories().unreadIncrement(-unreadCount, mute); +void History::removeFromChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed) { + t_assert(indexed != nullptr); + if (inChatList(list)) { + indexed->del(peer); + chatListLinks(list).clear(); + if (list == Dialogs::Mode::All && unreadCount()) { + App::histories().unreadIncrement(-unreadCount(), mute()); if (App::wnd()) App::wnd()->updateCounter(); } } } -void History::removeChatListEntryByLetter(QChar letter) { +void History::removeChatListEntryByLetter(Dialogs::Mode list, QChar letter) { t_assert(letter != 0); - if (inChatList()) { - _chatListLinks.remove(letter); + if (inChatList(list)) { + chatListLinks(list).remove(letter); } } -void History::addChatListEntryByLetter(QChar letter, DialogRow *row) { +void History::addChatListEntryByLetter(Dialogs::Mode list, QChar letter, Dialogs::Row *row) { t_assert(letter != 0); - if (inChatList()) { - _chatListLinks.insert(letter, row); + if (inChatList(list)) { + chatListLinks(list).insert(letter, row); } } void History::updateChatListEntry() const { if (MainWidget *m = App::main()) { - if (inChatList()) { - m->dlgUpdated(mainChatListLink()); + if (inChatList(Dialogs::Mode::All)) { + m->dlgUpdated(Dialogs::Mode::All, mainChatListLink(Dialogs::Mode::All)); + if (inChatList(Dialogs::Mode::Important)) { + m->dlgUpdated(Dialogs::Mode::Important, mainChatListLink(Dialogs::Mode::Important)); + } } } } @@ -2491,7 +2298,7 @@ void History::overviewSliceDone(int32 overviewIndex, const MTPmessages_Messages const QVector *v = 0; switch (result.type()) { case mtpc_messages_messages: { - const MTPDmessages_messages &d(result.c_messages_messages()); + const auto &d(result.c_messages_messages()); App::feedUsers(d.vusers); App::feedChats(d.vchats); v = &d.vmessages.c_vector().v; @@ -2499,7 +2306,7 @@ void History::overviewSliceDone(int32 overviewIndex, const MTPmessages_Messages } break; case mtpc_messages_messagesSlice: { - const MTPDmessages_messagesSlice &d(result.c_messages_messagesSlice()); + const auto &d(result.c_messages_messagesSlice()); App::feedUsers(d.vusers); App::feedChats(d.vchats); overviewCountData[overviewIndex] = d.vcount.v; @@ -2507,7 +2314,7 @@ void History::overviewSliceDone(int32 overviewIndex, const MTPmessages_Messages } break; case mtpc_messages_channelMessages: { - const MTPDmessages_channelMessages &d(result.c_messages_channelMessages()); + const auto &d(result.c_messages_channelMessages()); if (peer->isChannel()) { peer->asChannel()->ptsReceived(d.vpts.v); } else { @@ -2570,6 +2377,10 @@ void History::changeMsgId(MsgId oldId, MsgId newId) { void History::removeBlock(HistoryBlock *block) { 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) { @@ -2582,8 +2393,6 @@ void History::removeBlock(HistoryBlock *block) { History::~History() { clearOnDestroy(); - deleteAndMark(msgDraft); - deleteAndMark(editDraft); } int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) { @@ -2708,6 +2517,360 @@ void HistoryBlock::removeItem(HistoryItem *item) { } } +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(); + + int widthForText = _width - ((s - 1) * _st->buttonSkip()); + int widthOfText = 0; + for_const (const Button &button, row) { + widthOfText += qMax(button.text.maxWidth(), 1); + widthForText -= _st->minButtonWidth(button.type); + } + bool exact = (widthForText == widthOfText); + + float64 x = 0; + for (Button &button : row) { + int buttonw = qMax(button.text.maxWidth(), 1); + float64 textw = exact ? buttonw : (widthForText / float64(s)); + float64 minw = _st->minButtonWidth(button.type); + float64 w = minw + textw; + accumulate_max(w, 2 * float64(_st->buttonPadding())); + + 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(textw >= buttonw); + } + 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(); + for_const (const Button &button, row) { + rowWidth += qMax(button.text.maxWidth(), 1) + _st->minButtonWidth(button.type); + } + 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 != nullptr); + 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); + } + } +} + +ClickHandlerPtr ReplyKeyboard::getState(int x, int y) const { + t_assert(_width > 0); + + 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)) { + return button.link; + } + } + } + return ClickHandlerPtr(); +} + +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 auto &row, v) { + switch (row.type()) { + case mtpc_keyboardButtonRow: { + const auto &r(row.c_keyboardButtonRow()); + const auto &b(r.vbuttons.c_vector().v); + if (!b.isEmpty()) { + ButtonRow buttonRow; + buttonRow.reserve(b.size()); + for_const (const auto &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 }); + flags |= MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button; + } break; + } + } + if (!buttonRow.isEmpty()) rows.push_back(buttonRow); + } + } break; + } + } +} + +void HistoryMessageReplyMarkup::create(const MTPReplyMarkup &markup) { + flags = 0; + rows.clear(); + inlineKeyboard = nullptr; + + switch (markup.type()) { + case mtpc_replyKeyboardMarkup: { + const auto &d(markup.c_replyKeyboardMarkup()); + flags = d.vflags.v; + + createFromButtonRows(d.vrows.c_vector().v); + } break; + + case mtpc_replyInlineMarkup: { + const auto &d(markup.c_replyInlineMarkup()); + flags = MTPDreplyKeyboardMarkup::Flags(0) | MTPDreplyKeyboardMarkup_ClientFlag::f_inline; + + createFromButtonRows(d.vrows.c_vector().v); + } break; + + case mtpc_replyKeyboardHide: { + const auto &d(markup.c_replyKeyboardHide()); + flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_zero; + } break; + + case mtpc_replyKeyboardForceReply: { + const auto &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(); @@ -2720,13 +2883,17 @@ void HistoryMessageUnreadBar::init(int count) { _width = st::semiboldFont->width(_text); } -int HistoryMessageUnreadBar::height() const { - return st::unreadBarHeight; +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 + st::lineWidth, w, st::unreadBarHeight - 2 * st::lineWidth, st::unreadBarBG); - p.fillRect(0, y + st::unreadBarHeight - st::lineWidth, w, st::lineWidth, st::unreadBarBorder); + 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); @@ -2737,7 +2904,7 @@ void HistoryMessageUnreadBar::paint(Painter &p, int y, int w) const { } w = maxwidth; - p.drawText((w - _width) / 2, y + (st::unreadBarHeight - st::lineWidth - st::unreadBarFont->height) / 2 + st::unreadBarFont->ascent, _text); + 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) { @@ -2766,6 +2933,17 @@ void HistoryMessageDate::paint(Painter &p, int y, int w) const { p.drawText(left + st::msgServicePadding.left(), y + st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->ascent, _text); } +void HistoryMediaPtr::reset(HistoryMedia *p) { + if (_p) { + _p->detachFromParent(); + delete _p; + } + _p = p; + if (_p) { + _p->attachToParent(); + } +} + HistoryItem::HistoryItem(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime msgDate, int32 from) : HistoryElem() , y(0) , id(msgId) @@ -2780,6 +2958,26 @@ 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() { bool wasAtBottom = history()->loadedAtBottom(); _history->removeNotification(this); @@ -2797,9 +2995,10 @@ void HistoryItem::destroy() { history()->clearLastKeyboard(); if (App::main()) App::main()->updateBotKeyboard(history()); } - if ((!out() || isPost()) && unread() && history()->unreadCount > 0) { - history()->setUnreadCount(history()->unreadCount - 1); + if ((!out() || isPost()) && unread() && history()->unreadCount() > 0) { + history()->setUnreadCount(history()->unreadCount() - 1); } + Global::RefPendingRepaintItems().remove(this); delete this; } @@ -2920,7 +3119,7 @@ void HistoryItem::setUnreadBarCount(int count) { } void HistoryItem::setUnreadBarFreezed() { - if (auto *bar = Get()) { + if (auto bar = Get()) { bar->_freezed = true; } } @@ -2938,7 +3137,7 @@ void HistoryItem::clipCallback(ClipReaderNotification notification) { if (reader->paused()) { if (MainWidget *m = App::main()) { if (!m->isItemVisible(this)) { // stop animation if it is not visible - media->stopInline(this); + media->stopInline(); if (DocumentData *document = media->getDocument()) { // forget data from memory document->forget(); } @@ -3006,7 +3205,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() { @@ -3066,29 +3265,27 @@ namespace { } } -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(const ClickHandlerPtr &p, bool active) { + if (p == _savel || p == _cancell) { + if (active && !dataLoaded()) { + ensureAnimation(); + _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(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 { @@ -3106,7 +3303,7 @@ void HistoryFileMedia::setStatusSize(int32 newSize, int32 fullSize, int32 durati } } -void HistoryFileMedia::step_thumbOver(const HistoryItem *parent, float64 ms, bool timer) { +void HistoryFileMedia::step_thumbOver(float64 ms, bool timer) { float64 dt = ms / st::msgFileOverDuration; if (dt >= 1) { _animation->a_thumbOver.finish(); @@ -3116,13 +3313,13 @@ void HistoryFileMedia::step_thumbOver(const HistoryItem *parent, float64 ms, boo _animation->a_thumbOver.update(dt, anim::linear); } if (timer) { - Ui::repaintHistoryItem(parent); + Ui::repaintHistoryItem(_parent); } } -void HistoryFileMedia::step_radial(const HistoryItem *parent, uint64 ms, bool timer) { +void HistoryFileMedia::step_radial(uint64 ms, bool timer) { if (timer) { - Ui::repaintHistoryItem(parent); + Ui::repaintHistoryItem(_parent); } else { _animation->radial.update(dataProgress(), dataFinished(), ms); if (!_animation->radial.animating()) { @@ -3131,11 +3328,11 @@ void HistoryFileMedia::step_radial(const HistoryItem *parent, uint64 ms, bool ti } } -void HistoryFileMedia::ensureAnimation(const HistoryItem *parent) const { +void HistoryFileMedia::ensureAnimation() const { if (!_animation) { _animation = new AnimationData( - animation(parent, const_cast(this), &HistoryFileMedia::step_thumbOver), - animation(parent, const_cast(this), &HistoryFileMedia::step_radial)); + animation(const_cast(this), &HistoryFileMedia::step_thumbOver), + animation(const_cast(this), &HistoryFileMedia::step_radial)); } } @@ -3152,35 +3349,30 @@ HistoryFileMedia::~HistoryFileMedia() { deleteAndMark(_animation); } -HistoryPhoto::HistoryPhoto(PhotoData *photo, const QString &caption, const HistoryItem *parent) : HistoryFileMedia() +HistoryPhoto::HistoryPhoto(HistoryItem *parent, PhotoData *photo, const QString &caption) : HistoryFileMedia(parent) , _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)); + _caption.setText(st::msgFont, caption + _parent->skipBlock(), itemTextNoMonoOptions(_parent)); } init(); } -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)); +HistoryPhoto::HistoryPhoto(HistoryItem *parent, PeerData *chat, const MTPDphoto &photo, int32 width) : HistoryFileMedia(parent) +, _data(App::feedPhoto(photo)) { + setLinks(MakeShared(_data, chat), MakeShared(_data, chat), MakeShared(_data, chat)); _width = width; init(); } -HistoryPhoto::HistoryPhoto(const HistoryPhoto &other) : HistoryFileMedia() +HistoryPhoto::HistoryPhoto(HistoryItem *parent, const HistoryPhoto &other) : HistoryFileMedia(parent) , _data(other._data) , _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(); } @@ -3189,9 +3381,9 @@ void HistoryPhoto::init() { _data->thumb->load(); } -void HistoryPhoto::initDimensions(const HistoryItem *parent) { +void HistoryPhoto::initDimensions() { if (_caption.hasSkipBlock()) { - _caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); + _caption.setSkipBlock(_parent->skipBlockWidth(), _parent->skipBlockHeight()); } int32 tw = convertScale(_data->full->width()), th = convertScale(_data->full->height()); @@ -3207,10 +3399,10 @@ void HistoryPhoto::initDimensions(const HistoryItem *parent) { th = st::maxMediaSize; } - if (parent->toHistoryMessage()) { - bool bubble = parent->hasBubble(); + if (_parent->toHistoryMessage()) { + bool bubble = _parent->hasBubble(); - int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + int32 minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); int32 maxActualWidth = qMax(tw, minWidth); _maxw = qMax(maxActualWidth, th); _minh = qMax(th, int32(st::minPhotoSize)); @@ -3227,10 +3419,10 @@ void HistoryPhoto::initDimensions(const HistoryItem *parent) { } } -int32 HistoryPhoto::resize(int32 width, const HistoryItem *parent) { - bool bubble = parent->hasBubble(); +int HistoryPhoto::resizeGetHeight(int width) { + 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; @@ -3257,35 +3449,36 @@ 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(); } } return _height; } -void HistoryPhoto::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { +void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; - _data->automaticLoad(parent); + _data->automaticLoad(_parent); + bool selected = (selection == FullSelection); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); - bool notChild = (parent->getMedia() == this); - int32 skipx = 0, skipy = 0, width = _width, height = _height; - bool bubble = parent->hasBubble(); - bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; + bool notChild = (_parent->getMedia() == this); + int skipx = 0, skipy = 0, width = _width, height = _height; + bool bubble = _parent->hasBubble(); + 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); + ensureAnimation(); if (!_animation->radial.animating()) { _animation->radial.start(_data->progress()); } @@ -3328,7 +3521,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); } @@ -3362,32 +3555,32 @@ void HistoryPhoto::draw(Painter &p, const HistoryItem *parent, const QRect &r, b // date if (_caption.isEmpty()) { - if (notChild && (_data->uploading() || App::hoveredItem() == parent)) { + if (notChild && (_data->uploading() || App::hoveredItem() == _parent)) { int32 fullRight = skipx + width, fullBottom = skipy + height; - parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); + _parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); } } else { p.setPen(st::black); - _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw); + _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); } } -void HistoryPhoto::getState(TextLinkPtr &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(); +HistoryTextState HistoryPhoto::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; + 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; - _caption.getState(lnk, inText, x - st::msgPadding.left(), y - height, captionw); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; + result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText()); + return result; } height -= st::mediaCaptionSkip; } @@ -3396,35 +3589,36 @@ void HistoryPhoto::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { if (_data->uploading()) { - lnk = _cancell; + result.link = _cancell; } else if (_data->loaded()) { - lnk = _openl; + result.link = _openl; } else if (_data->loading()) { DelayedStorageImage *delayed = _data->full->toDelayedStorageImage(); if (!delayed || !delayed->location().isNull()) { - lnk = _cancell; + result.link = _cancell; } } else { - lnk = _savel; + result.link = _savel; } - if (_caption.isEmpty() && parent->getMedia() == this) { + if (_caption.isEmpty() && _parent->getMedia() == this) { int32 fullRight = skipx + width, fullBottom = skipy + height; - bool inDate = parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); + bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } } - return; + return result; } + return result; } -void HistoryPhoto::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { +void HistoryPhoto::updateSentMedia(const MTPMessageMedia &media) { if (media.type() == mtpc_messageMediaPhoto) { - const MTPPhoto &photo(media.c_messageMediaPhoto().vphoto); + const auto &photo(media.c_messageMediaPhoto().vphoto); App::feedPhoto(photo, _data); if (photo.type() == mtpc_photo) { - const QVector &sizes(photo.c_photo().vsizes.c_vector().v); + const auto &sizes(photo.c_photo().vsizes.c_vector().v); int32 max = 0; const MTPDfileLocation *maxLocation = 0; for (int32 i = 0, l = sizes.size(); i < l; ++i) { @@ -3466,55 +3660,68 @@ void HistoryPhoto::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) } } -void HistoryPhoto::attachToItem(HistoryItem *item) { - App::regPhotoItem(_data, item); +bool HistoryPhoto::needReSetInlineResultMedia(const MTPMessageMedia &media) { + if (media.type() == mtpc_messageMediaPhoto) { + if (PhotoData *existing = App::feedPhoto(media.c_messageMediaPhoto().vphoto)) { + if (existing == _data) { + return false; + } else { + // collect data + } + } + } + return false; } -void HistoryPhoto::detachFromItem(HistoryItem *item) { - App::unregPhotoItem(_data, item); +void HistoryPhoto::attachToParent() { + App::regPhotoItem(_data, _parent); +} + +void HistoryPhoto::detachFromParent() { + App::unregPhotoItem(_data, _parent); } const QString HistoryPhoto::inDialogsText() const { - return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.original(0, 0xFFFF, Text::ExpandLinksNone); + return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.original(AllTextSelection, Text::ExpandLinksNone); } const QString HistoryPhoto::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_photo) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]"); + return qsl("[ ") + lang(lng_in_dlg_photo) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(AllTextSelection, Text::ExpandLinksAll))) + qsl(" ]"); } ImagePtr HistoryPhoto::replyPreview() { return _data->makeReplyPreview(); } -HistoryVideo::HistoryVideo(DocumentData *document, const QString &caption, const HistoryItem *parent) : HistoryFileMedia() +HistoryVideo::HistoryVideo(HistoryItem *parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent) , _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)); + _caption.setText(st::msgFont, caption + _parent->skipBlock(), itemTextNoMonoOptions(_parent)); } - setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data)); + setDocumentLinks(_data); setStatusSize(FileStatusSizeReady); _data->thumb->load(); } -HistoryVideo::HistoryVideo(const HistoryVideo &other) : HistoryFileMedia() +HistoryVideo::HistoryVideo(HistoryItem *parent, const HistoryVideo &other) : HistoryFileMedia(parent) , _data(other._data) , _thumbw(other._thumbw) , _caption(other._caption) { - setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data)); + setDocumentLinks(_data); setStatusSize(other._statusSize); } -void HistoryVideo::initDimensions(const HistoryItem *parent) { - bool bubble = parent->hasBubble(); +void HistoryVideo::initDimensions() { + bool bubble = _parent->hasBubble(); if (_caption.hasSkipBlock()) { - _caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); + _caption.setSkipBlock(_parent->skipBlockWidth(), _parent->skipBlockHeight()); } int32 tw = convertScale(_data->thumb->width()), th = convertScale(_data->thumb->height()); @@ -3530,7 +3737,7 @@ void HistoryVideo::initDimensions(const HistoryItem *parent) { } _thumbw = qMax(tw, 1); - int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + int32 minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); _maxw = qMax(_thumbw, int32(minWidth)); _minh = qMax(th, int32(st::minPhotoSize)); @@ -3543,10 +3750,10 @@ void HistoryVideo::initDimensions(const HistoryItem *parent) { } } -int32 HistoryVideo::resize(int32 width, const HistoryItem *parent) { - bool bubble = parent->hasBubble(); +int HistoryVideo::resizeGetHeight(int width) { + 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; } @@ -3567,40 +3774,41 @@ int32 HistoryVideo::resize(int32 width, const HistoryItem *parent) { } _thumbw = qMax(tw, 1); - int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); - _width = qMax(_thumbw, int32(minWidth)); - _height = qMax(th, int32(st::minPhotoSize)); + 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(); } } return _height; } -void HistoryVideo::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { +void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; - _data->automaticLoad(parent); + _data->automaticLoad(_parent); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + bool selected = (selection == FullSelection); - int32 skipx = 0, skipy = 0, width = _width, height = _height; - bool bubble = parent->hasBubble(); - bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; + int skipx = 0, skipy = 0, width = _width, height = _height; + bool bubble = _parent->hasBubble(); + 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); + ensureAnimation(); if (!_animation->radial.animating()) { _animation->radial.start(_data->progress()); } } - updateStatusText(parent); + updateStatusText(); bool radial = isRadialAnimation(ms); if (bubble) { @@ -3631,7 +3839,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); } @@ -3667,23 +3875,25 @@ void HistoryVideo::draw(Painter &p, const HistoryItem *parent, const QRect &r, b // date if (_caption.isEmpty()) { - if (parent->getMedia() == this) { + if (_parent->getMedia() == this) { int32 fullRight = skipx + width, fullBottom = skipy + height; - parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); + _parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); } } else { p.setPen(st::black); - _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw); + _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); } } -void HistoryVideo::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryVideo::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; bool loaded = _data->loaded(); int32 skipx = 0, skipy = 0, width = _width, height = _height; - bool bubble = parent->hasBubble(); + bool bubble = _parent->hasBubble(); if (bubble) { skipx = st::mediaPadding.left(); @@ -3692,9 +3902,7 @@ void HistoryVideo::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x int32 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; - _caption.getState(lnk, inText, x - st::msgPadding.left(), y - height, captionw); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; + result = _caption.getState(x - st::msgPadding.left(), y - height, captionw); } height -= st::mediaCaptionSkip; } @@ -3702,16 +3910,17 @@ void HistoryVideo::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x height -= skipy + st::mediaPadding.bottom(); } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { - lnk = loaded ? _openl : (_data->loading() ? _cancell : _savel); - if (_caption.isEmpty() && parent->getMedia() == this) { + result.link = loaded ? _openl : (_data->loading() ? _cancell : _savel); + if (_caption.isEmpty() && _parent->getMedia() == this) { int32 fullRight = skipx + width, fullBottom = skipy + height; - bool inDate = parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); + bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } } - return; + return result; } + return result; } void HistoryVideo::setStatusSize(int32 newSize) const { @@ -3719,14 +3928,14 @@ void HistoryVideo::setStatusSize(int32 newSize) const { } const QString HistoryVideo::inDialogsText() const { - return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.original(0, 0xFFFF, Text::ExpandLinksNone); + return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.original(AllTextSelection, Text::ExpandLinksNone); } const QString HistoryVideo::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_video) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]"); + return qsl("[ ") + lang(lng_in_dlg_video) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(AllTextSelection, Text::ExpandLinksAll))) + qsl(" ]"); } -void HistoryVideo::updateStatusText(const HistoryItem *parent) const { +void HistoryVideo::updateStatusText() const { bool showPause = false; int32 statusSize = 0, realDuration = 0; if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { @@ -3745,12 +3954,16 @@ void HistoryVideo::updateStatusText(const HistoryItem *parent) const { } } -void HistoryVideo::attachToItem(HistoryItem *item) { - App::regDocumentItem(_data, item); +void HistoryVideo::attachToParent() { + App::regDocumentItem(_data, _parent); } -void HistoryVideo::detachFromItem(HistoryItem *item) { - App::unregDocumentItem(_data, item); +void HistoryVideo::detachFromParent() { + App::unregDocumentItem(_data, _parent); +} + +bool HistoryVideo::needReSetInlineResultMedia(const MTPMessageMedia &media) { + return needReSetInlineResultDocument(media, _data); } ImagePtr HistoryVideo::replyPreview() { @@ -3786,32 +3999,30 @@ void HistoryDocumentVoice::checkPlaybackFinished() const { } } -HistoryDocument::HistoryDocument(DocumentData *document, const QString &caption, const HistoryItem *parent) : HistoryFileMedia() -, _parent(nullptr) +HistoryDocument::HistoryDocument(HistoryItem *parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent) , _data(document) { - createInterfaces(!caption.isEmpty()); - if (auto *named = Get()) { + createComponents(!caption.isEmpty()); + if (auto named = Get()) { named->_name = documentName(_data); named->_namew = st::semiboldFont->width(named->_name); } - setLinks(new DocumentOpenLink(_data), _data->voice() ? (ITextLink*)(new VoiceSaveLink(_data)) : new DocumentSaveLink(_data), new DocumentCancelLink(_data)); + setDocumentLinks(_data); setStatusSize(FileStatusSizeReady); - if (auto *captioned = Get()) { - captioned->_caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); + if (auto captioned = Get()) { + captioned->_caption.setText(st::msgFont, caption + _parent->skipBlock(), itemTextNoMonoOptions(_parent)); } } -HistoryDocument::HistoryDocument(const HistoryDocument &other) : HistoryFileMedia() +HistoryDocument::HistoryDocument(HistoryItem *parent, const HistoryDocument &other) : HistoryFileMedia(parent) , Composer() -, _parent(nullptr) , _data(other._data) { - auto *captioned = other.Get(); - createInterfaces(captioned != 0); - if (auto *named = Get()) { - if (auto *othernamed = other.Get()) { + 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 { @@ -3820,7 +4031,7 @@ HistoryDocument::HistoryDocument(const HistoryDocument &other) : HistoryFileMedi } } - setLinks(new DocumentOpenLink(_data), _data->voice() ? (ITextLink*)(new VoiceSaveLink(_data)) : new DocumentSaveLink(_data), new DocumentCancelLink(_data)); + setDocumentLinks(_data); setStatusSize(other._statusSize); @@ -3829,7 +4040,7 @@ HistoryDocument::HistoryDocument(const HistoryDocument &other) : HistoryFileMedi } } -void HistoryDocument::createInterfaces(bool caption) { +void HistoryDocument::createComponents(bool caption) { uint64 mask = 0; if (_data->voice()) { mask |= HistoryDocumentVoice::Bit(); @@ -3843,21 +4054,19 @@ void HistoryDocument::createInterfaces(bool caption) { mask |= HistoryDocumentCaptioned::Bit(); } UpdateComponents(mask); - if (auto *thumbed = Get()) { - thumbed->_linksavel.reset(new DocumentSaveLink(_data)); - thumbed->_linkcancell.reset(new DocumentCancelLink(_data)); + if (auto thumbed = Get()) { + thumbed->_linksavel.reset(new DocumentSaveClickHandler(_data)); + thumbed->_linkcancell.reset(new DocumentCancelClickHandler(_data)); } } -void HistoryDocument::initDimensions(const HistoryItem *parent) { - _parent = parent; - - auto *captioned = Get(); +void HistoryDocument::initDimensions() { + auto captioned = Get(); if (captioned && captioned->_caption.hasSkipBlock()) { - captioned->_caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); + captioned->_caption.setSkipBlock(_parent->skipBlockWidth(), _parent->skipBlockHeight()); } - auto *thumbed = Get(); + auto thumbed = Get(); if (thumbed) { _data->thumb->load(); int32 tw = convertScale(_data->thumb->width()), th = convertScale(_data->thumb->height()); @@ -3879,17 +4088,17 @@ void HistoryDocument::initDimensions(const HistoryItem *parent) { tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); tright = st::msgFileThumbPadding.left(); int32 unread = _data->voice() ? (st::mediaUnreadSkip + st::mediaUnreadSize) : 0; - _maxw = qMax(_maxw, tleft + documentMaxStatusWidth(_data) + unread + parent->skipBlockWidth() + st::msgPadding.right()); + _maxw = qMax(_maxw, tleft + documentMaxStatusWidth(_data) + unread + _parent->skipBlockWidth() + st::msgPadding.right()); } - if (auto *named = Get()) { + if (auto named = Get()) { _maxw = qMax(tleft + named->_namew + tright, _maxw); _maxw = qMin(_maxw, int(st::msgMaxWidth)); } if (thumbed) { _minh = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); - if (!captioned && parent->Has()) { + if (!captioned && _parent->Has()) { _minh += st::msgDateFont->height - st::msgDateDelta.y(); } } else { @@ -3903,10 +4112,10 @@ void HistoryDocument::initDimensions(const HistoryItem *parent) { } } -int32 HistoryDocument::resize(int32 width, const HistoryItem *parent) { - auto *captioned = Get(); +int HistoryDocument::resizeGetHeight(int width) { + auto captioned = Get(); if (!captioned) { - return HistoryFileMedia::resize(width, parent); + return HistoryFileMedia::resizeGetHeight(width); } _width = qMin(width, _maxw); @@ -3920,27 +4129,28 @@ int32 HistoryDocument::resize(int32 width, const HistoryItem *parent) { return _height; } -void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { +void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; - _data->automaticLoad(parent); + _data->automaticLoad(_parent); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + bool selected = (selection == FullSelection); - int32 captionw = _width - st::msgPadding.left() - st::msgPadding.right(); + int captionw = _width - st::msgPadding.left() - st::msgPadding.right(); - bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; + bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; if (displayLoading) { - ensureAnimation(parent); + ensureAnimation(); if (!_animation->radial.animating()) { _animation->radial.start(_data->progress()); } } - bool showPause = updateStatusText(parent); + bool showPause = updateStatusText(); bool radial = isRadialAnimation(ms); - int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; - if (auto *thumbed = Get()) { + 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(); @@ -3966,7 +4176,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()); @@ -3993,8 +4203,8 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r } if (_data->status != FileUploadFailed) { - const TextLinkPtr &lnk((_data->loading() || _data->status == FileUploading) ? thumbed->_linkcancell : thumbed->_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, thumbed->_link, thumbed->_linkw); @@ -4014,7 +4224,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)); } @@ -4048,7 +4258,7 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r } int32 namewidth = _width - nameleft - nameright; - if (auto *voice = Get()) { + if (auto voice = Get()) { const VoiceWaveform *wf = 0; uchar norm_value = 0; if (_data->voice()) { @@ -4070,7 +4280,7 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r 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()) { + if (!outbg && !voice->_playback && _parent->isMediaUnread()) { activew = availw; } int32 bar_count = qMin(availw / int32(st::msgWaveformBar + st::msgWaveformSkip), wf_size); @@ -4107,7 +4317,7 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r sum_i += bar_count; } } - } else if (auto *named = Get()) { + } else if (auto named = Get()) { p.setFont(st::semiboldFont); p.setPen(st::black); if (namewidth < named->_namew) { @@ -4122,7 +4332,7 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r p.setPen(status); p.drawTextLeft(nameleft, statustop, _width, _statusText); - if (parent->isMediaUnread()) { + if (_parent->isMediaUnread()) { int32 w = st::normalFont->width(_statusText); if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= namewidth) { p.setPen(Qt::NoPen); @@ -4134,22 +4344,24 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r } } - if (auto *captioned = Get()) { + if (auto captioned = Get()) { p.setPen(st::black); - captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw); + captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection); } } -void HistoryDocument::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; - bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; + + bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; bool loaded = _data->loaded(); - bool showPause = updateStatusText(parent); + bool showPause = updateStatusText(); int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; - if (auto *thumbed = Get()) { + 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(); @@ -4157,14 +4369,14 @@ void HistoryDocument::getState(TextLinkPtr &lnk, HistoryCursorState &state, int3 QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, _width)); if ((_data->loading() || _data->uploading() || !loaded) && rthumb.contains(x, y)) { - lnk = (_data->loading() || _data->uploading()) ? _cancell : _savel; - return; + result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel; + return result; } if (_data->status != FileUploadFailed) { if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, _width).contains(x, y)) { - lnk = (_data->loading() || _data->uploading()) ? thumbed->_linkcancell : thumbed->_linksavel; - return; + result.link = (_data->loading() || _data->uploading()) ? thumbed->_linkcancell : thumbed->_linksavel; + return result; } } } else { @@ -4172,40 +4384,42 @@ void HistoryDocument::getState(TextLinkPtr &lnk, HistoryCursorState &state, int3 QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width)); if ((_data->loading() || _data->uploading() || !loaded) && inner.contains(x, y)) { - lnk = (_data->loading() || _data->uploading()) ? _cancell : _savel; - return; + result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel; + return result; } } int32 height = _height; - if (auto *captioned = Get()) { + if (auto captioned = Get()) { if (y >= bottom) { - bool inText = false; - captioned->_caption.getState(lnk, inText, x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right()); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; + result = captioned->_caption.getState(x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right(), request.forText()); + return result; } 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; - return; + if (x >= 0 && y >= 0 && x < _width && y < height && !_data->loading() && !_data->uploading() && _data->isValid()) { + result.link = _openl; + return result; } + return result; } const QString HistoryDocument::inDialogsText() const { QString result; if (Has()) { result = lang(lng_in_dlg_audio); - } else if (_data->song()) { - result = lang(lng_in_dlg_audio_file); + } else if (auto song = _data->song()) { + result = documentName(_data); + if (result.isEmpty()) { + result = lang(lng_in_dlg_audio_file); + } } else { - auto *named = Get(); + auto named = Get(); result = (!named || named->_name.isEmpty()) ? lang(lng_in_dlg_file) : named->_name; } - if (auto *captioned = Get()) { + if (auto captioned = Get()) { if (!captioned->_caption.isEmpty()) { - result.append(' ').append(captioned->_caption.original(0, 0xFFFF, Text::ExpandLinksNone)); + result.append(' ').append(captioned->_caption.original(AllTextSelection, Text::ExpandLinksNone)); } } return result; @@ -4220,14 +4434,14 @@ const QString HistoryDocument::inHistoryText() const { } else { result = lang(lng_in_dlg_file); } - if (auto *named = Get()) { + if (auto named = Get()) { if (!named->_name.isEmpty()) { result.append(qsl(" : ")).append(named->_name); } } - if (auto *captioned = Get()) { + if (auto captioned = Get()) { if (!captioned->_caption.isEmpty()) { - result.append(qsl(", ")).append(captioned->_caption.original(0, 0xFFFF, Text::ExpandLinksAll)); + result.append(qsl(", ")).append(captioned->_caption.original(AllTextSelection, Text::ExpandLinksAll)); } } return qsl("[ ") + result.append(qsl(" ]")); @@ -4236,7 +4450,7 @@ const QString HistoryDocument::inHistoryText() const { void HistoryDocument::setStatusSize(int32 newSize, qint64 realDuration) const { 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 (auto thumbed = Get()) { if (_statusSize == FileStatusSizeReady) { thumbed->_link = lang(lng_media_download).toUpper(); } else if (_statusSize == FileStatusSizeLoaded) { @@ -4252,7 +4466,7 @@ void HistoryDocument::setStatusSize(int32 newSize, qint64 realDuration) const { } } -bool HistoryDocument::updateStatusText(const HistoryItem *parent) const { +bool HistoryDocument::updateStatusText() const { bool showPause = false; int32 statusSize = 0, realDuration = 0; if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { @@ -4271,8 +4485,8 @@ bool HistoryDocument::updateStatusText(const HistoryItem *parent) const { audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); } - if (playing.msgId == parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - if (auto *voice = Get()) { + if (playing == AudioMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + if (auto voice = Get()) { bool was = voice->_playback; voice->ensurePlayback(this); if (!was || playingPosition != voice->_playback->_position) { @@ -4292,7 +4506,7 @@ bool HistoryDocument::updateStatusText(const HistoryItem *parent) const { showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); } else { statusSize = FileStatusSizeLoaded; - if (auto *voice = Get()) { + if (auto voice = Get()) { voice->checkPlaybackFinished(); } } @@ -4305,14 +4519,14 @@ bool HistoryDocument::updateStatusText(const HistoryItem *parent) const { audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); } - if (playing.msgId == parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + if (playing == SongMsgId(_data, _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; } - if (!showPause && playing.msgId == parent->fullId() && App::main() && App::main()->player()->seekingSong(playing)) { + if (!showPause && (playing == SongMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) { showPause = true; } } else { @@ -4328,7 +4542,7 @@ bool HistoryDocument::updateStatusText(const HistoryItem *parent) const { } void HistoryDocument::step_voiceProgress(float64 ms, bool timer) { - if (auto *voice = Get()) { + if (auto voice = Get()) { if (voice->_playback) { float64 dt = ms / (2 * AudioVoiceMsgUpdateView); if (dt >= 1) { @@ -4342,15 +4556,15 @@ void HistoryDocument::step_voiceProgress(float64 ms, bool timer) { } } -void HistoryDocument::attachToItem(HistoryItem *item) { - App::regDocumentItem(_data, item); +void HistoryDocument::attachToParent() { + App::regDocumentItem(_data, _parent); } -void HistoryDocument::detachFromItem(HistoryItem *item) { - App::unregDocumentItem(_data, item); +void HistoryDocument::detachFromParent() { + App::unregDocumentItem(_data, _parent); } -void HistoryDocument::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { +void HistoryDocument::updateSentMedia(const MTPMessageMedia &media) { if (media.type() == mtpc_messageMediaDocument) { App::feedDocument(media.c_messageMediaDocument().vdocument, _data); if (!_data->data().isEmpty()) { @@ -4363,47 +4577,48 @@ void HistoryDocument::updateFrom(const MTPMessageMedia &media, HistoryItem *pare } } +bool HistoryDocument::needReSetInlineResultMedia(const MTPMessageMedia &media) { + return needReSetInlineResultDocument(media, _data); +} + ImagePtr HistoryDocument::replyPreview() { return _data->makeReplyPreview(); } -HistoryGif::HistoryGif(DocumentData *document, const QString &caption, const HistoryItem *parent) : HistoryFileMedia() -, _parent(nullptr) +HistoryGif::HistoryGif(HistoryItem *parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent) , _data(document) , _thumbw(1) , _thumbh(1) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) , _gif(nullptr) { - setLinks(new GifOpenLink(_data), new GifOpenLink(_data), new DocumentCancelLink(_data)); + setDocumentLinks(_data, true); setStatusSize(FileStatusSizeReady); if (!caption.isEmpty()) { - _caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); + _caption.setText(st::msgFont, caption + _parent->skipBlock(), itemTextNoMonoOptions(_parent)); } _data->thumb->load(); } -HistoryGif::HistoryGif(const HistoryGif &other) : HistoryFileMedia() -, _parent(nullptr) +HistoryGif::HistoryGif(HistoryItem *parent, const HistoryGif &other) : HistoryFileMedia(parent) , _data(other._data) , _thumbw(other._thumbw) , _thumbh(other._thumbh) , _caption(other._caption) , _gif(nullptr) { - setLinks(new GifOpenLink(_data), new GifOpenLink(_data), new DocumentCancelLink(_data)); + setDocumentLinks(_data, true); setStatusSize(other._statusSize); } -void HistoryGif::initDimensions(const HistoryItem *parent) { - _parent = parent; +void HistoryGif::initDimensions() { if (_caption.hasSkipBlock()) { - _caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); + _caption.setSkipBlock(_parent->skipBlockWidth(), _parent->skipBlockHeight()); } - bool bubble = parent->hasBubble(); + bool bubble = _parent->hasBubble(); int32 tw = 0, th = 0; if (gif() && _gif->state() == ClipError) { if (!_gif->autoplay()) { @@ -4439,7 +4654,7 @@ 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())); + _maxw = qMax(_maxw, _parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); if (!gif() || !_gif->ready()) { _maxw = qMax(_maxw, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); } @@ -4452,10 +4667,10 @@ void HistoryGif::initDimensions(const HistoryItem *parent) { } } -int32 HistoryGif::resize(int32 width, const HistoryItem *parent) { - bool bubble = parent->hasBubble(); +int HistoryGif::resizeGetHeight(int width) { + 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()); @@ -4490,7 +4705,7 @@ 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())); + _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); @@ -4509,31 +4724,33 @@ int32 HistoryGif::resize(int32 width, const HistoryItem *parent) { return _height; } -void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { +void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; - _data->automaticLoad(parent); - bool loaded = _data->loaded(), displayLoading = (parent->id < 0) || _data->displayLoading(); + _data->automaticLoad(_parent); + bool loaded = _data->loaded(), displayLoading = (_parent->id < 0) || _data->displayLoading(); + bool selected = (selection == FullSelection); + if (loaded && !gif() && _gif != BadClipReader && cAutoPlayGif()) { - Ui::autoplayMediaInlineAsync(parent->fullId()); + Ui::autoplayMediaInlineAsync(_parent->fullId()); } int32 skipx = 0, skipy = 0, width = _width, height = _height; - bool bubble = parent->hasBubble(); - bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; + bool bubble = _parent->hasBubble(); + bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); bool animating = (gif() && _gif->started()); - if (!animating || parent->id < 0) { + if (!animating || _parent->id < 0) { if (displayLoading) { - ensureAnimation(parent); + ensureAnimation(); if (!_animation->radial.animating()) { _animation->radial.start(dataProgress()); } } - updateStatusText(parent); + updateStatusText(); } bool radial = isRadialAnimation(ms); @@ -4562,7 +4779,7 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo } if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == BadClipReader)) { - float64 radialOpacity = (radial && loaded && parent->id > 0) ? _animation->radial.opacity() : 1; + float64 radialOpacity = (radial && loaded && _parent->id > 0) ? _animation->radial.opacity() : 1; QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); p.setPen(Qt::NoPen); if (selected) { @@ -4572,7 +4789,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()); @@ -4586,7 +4803,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 || _data->uploading()) { + if (_parent->id > 0 || _data->uploading()) { icon = (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); } } else { @@ -4601,7 +4818,7 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::msgInBgSelected : st::msgInBg); } - if (!animating || parent->id < 0) { + if (!animating || _parent->id < 0) { int32 statusX = skipx + st::msgDateImgDelta + st::msgDateImgPadding.x(), statusY = skipy + st::msgDateImgDelta + st::msgDateImgPadding.y(); int32 statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); int32 statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); @@ -4614,17 +4831,19 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo 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)) { + _caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); + } 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); + _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 { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int32 skipx = 0, skipy = 0, width = _width, height = _height; - bool bubble = parent->hasBubble(); + bool bubble = _parent->hasBubble(); if (bubble) { skipx = st::mediaPadding.left(); @@ -4633,10 +4852,8 @@ void HistoryGif::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 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; - _caption.getState(lnk, inText, x - st::msgPadding.left(), y - height, captionw); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; + result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText()); + return result; } height -= st::mediaCaptionSkip; } @@ -4645,34 +4862,35 @@ void HistoryGif::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { if (_data->uploading()) { - lnk = _cancell; + result.link = _cancell; } else if (!gif() || !cAutoPlayGif()) { - lnk = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel); + result.link = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel); } - if (parent->getMedia() == this) { + if (_parent->getMedia() == this) { int32 fullRight = skipx + width, fullBottom = skipy + height; - bool inDate = parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); + bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } } - return; + return result; } + return result; } const QString HistoryGif::inDialogsText() const { - return qsl("GIF") + (_caption.isEmpty() ? QString() : (' ' + _caption.original(0, 0xFFFF, Text::ExpandLinksNone))); + return qsl("GIF") + (_caption.isEmpty() ? QString() : (' ' + _caption.original(AllTextSelection, Text::ExpandLinksNone))); } const QString HistoryGif::inHistoryText() const { - return qsl("[ GIF ") + (_caption.isEmpty() ? QString() : (_caption.original(0, 0xFFFF, Text::ExpandLinksAll) + ' ')) + qsl(" ]"); + return qsl("[ GIF ") + (_caption.isEmpty() ? QString() : (_caption.original(AllTextSelection, Text::ExpandLinksAll) + ' ')) + qsl(" ]"); } void HistoryGif::setStatusSize(int32 newSize) const { HistoryFileMedia::setStatusSize(newSize, _data->size, -2, 0); } -void HistoryGif::updateStatusText(const HistoryItem *parent) const { +void HistoryGif::updateStatusText() const { bool showPause = false; int32 statusSize = 0, realDuration = 0; if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { @@ -4691,47 +4909,51 @@ void HistoryGif::updateStatusText(const HistoryItem *parent) const { } } -void HistoryGif::attachToItem(HistoryItem *item) { - App::regDocumentItem(_data, item); +void HistoryGif::attachToParent() { + App::regDocumentItem(_data, _parent); } -void HistoryGif::detachFromItem(HistoryItem *item) { - App::unregDocumentItem(_data, item); +void HistoryGif::detachFromParent() { + App::unregDocumentItem(_data, _parent); } -void HistoryGif::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { +void HistoryGif::updateSentMedia(const MTPMessageMedia &media) { if (media.type() == mtpc_messageMediaDocument) { App::feedDocument(media.c_messageMediaDocument().vdocument, _data); } } +bool HistoryGif::needReSetInlineResultMedia(const MTPMessageMedia &media) { + return needReSetInlineResultDocument(media, _data); +} + ImagePtr HistoryGif::replyPreview() { return _data->makeReplyPreview(); } -bool HistoryGif::playInline(HistoryItem *parent, bool autoplay) { +bool HistoryGif::playInline(bool autoplay) { if (gif()) { - stopInline(parent); + stopInline(); } else { if (!cAutoPlayGif()) { App::stopGifItems(); } - _gif = new ClipReader(_data->location(), _data->data(), func(parent, &HistoryItem::clipCallback)); - App::regGifItem(_gif, parent); + _gif = new ClipReader(_data->location(), _data->data(), func(_parent, &HistoryItem::clipCallback)); + App::regGifItem(_gif, _parent); if (gif()) _gif->setAutoplay(); } return true; } -void HistoryGif::stopInline(HistoryItem *parent) { +void HistoryGif::stopInline() { if (gif()) { App::unregGifItem(_gif); delete _gif; _gif = 0; } - parent->setPendingInitDimensions(); - Notify::historyItemLayoutChanged(parent); + _parent->setPendingInitDimensions(); + Notify::historyItemLayoutChanged(_parent); } HistoryGif::~HistoryGif() { @@ -4753,7 +4975,34 @@ bool HistoryGif::dataLoaded() const { return (!_parent || _parent->id > 0) ? _data->loaded() : false; } -HistorySticker::HistorySticker(DocumentData *document) : HistoryMedia() +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(HistoryItem *parent, DocumentData *document) : HistoryMedia(parent) , _pixw(1) , _pixh(1) , _data(document) @@ -4764,7 +5013,10 @@ HistorySticker::HistorySticker(DocumentData *document) : HistoryMedia() } } -void HistorySticker::initDimensions(const HistoryItem *parent) { +void HistorySticker::initDimensions() { + 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) { @@ -4779,34 +5031,45 @@ void HistorySticker::initDimensions(const HistoryItem *parent) { if (_pixh < 1) _pixh = 1; _maxw = qMax(_pixw, int16(st::minPhotoSize)); _minh = qMax(_pixh, int16(st::minPhotoSize)); - if (auto *reply = parent->Get()) { - _maxw += st::msgReplyPadding.left() + reply->replyToWidth(); + if (_parent->getMedia() == this) { + _maxw += additionalWidth(); } _height = _minh; } -int32 HistorySticker::resize(int32 width, const HistoryItem *parent) { // return new height +int HistorySticker::resizeGetHeight(int width) { // return new height _width = qMin(width, _maxw); - if (auto *reply = parent->Get()) { - int32 usew = _maxw - st::msgReplyPadding.left() - reply->replyToWidth(); - int32 rw = _width - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.right(); - reply->resize(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; } -void HistorySticker::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { +void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; _data->checkSticker(); bool loaded = _data->loaded(); + bool selected = (selection == FullSelection); - bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; + bool out = _parent->out(), isPost = _parent->isPost(), childmedia = (_parent->getMedia() != this); - int32 usew = _maxw, usex = 0; - auto *reply = parent->Get(); - if (reply) { - usew -= st::msgReplyPadding.left() + reply->replyToWidth(); + 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; @@ -4828,55 +5091,108 @@ 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 = isPost ? (usew + st::msgReplyPadding.left()) : (out ? 0 : (usew + st::msgReplyPadding.left())), ry = _height - rh; - if (rtl()) rx = _width - rx - rw; - - App::roundRect(p, rx, ry, rw, rh, selected ? App::msgServiceSelectBg() : App::msgServiceBg(), selected ? ServiceSelectedCorners : ServiceCorners); - - HistoryMessageReply::PaintFlags flags = 0; - if (selected) { - flags |= HistoryMessageReply::PaintSelected; + 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; + + 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); } - reply->paint(p, parent, rx + st::msgReplyPadding.left(), ry, rw - st::msgReplyPadding.left() - st::msgReplyPadding.right(), flags); } } } -void HistorySticker::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistorySticker::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; - bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; + bool out = _parent->out(), isPost = _parent->isPost(), childmedia = (_parent->getMedia() != this); - int32 usew = _maxw, usex = 0; - auto *reply = parent->Get(); - if (reply) { - usew -= reply->replyToWidth(); + 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 = isPost ? (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) { + result.link = via->_lnk; + return result; + } + 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) { + result.link = reply->replyToLink(); + return result; + } } } - if (parent->getMedia() == this) { - bool inDate = parent->pointInTime(usex + usew, _height, x, y, InfoDisplayOverImage); + if (_parent->getMedia() == this) { + bool inDate = _parent->pointInTime(usex + usew, _height, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } } + + int pixLeft = usex + (usew - _pixw) / 2, pixTop = (_minh - _pixh) / 2; + if (x >= pixLeft && x < pixLeft + _pixw && y >= pixTop && y < pixTop + _pixh) { + result.link = _packLink; + return result; + } + return result; } const QString HistorySticker::inDialogsText() const { @@ -4887,15 +5203,15 @@ const QString HistorySticker::inHistoryText() const { return qsl("[ ") + inDialogsText() + qsl(" ]"); } -void HistorySticker::attachToItem(HistoryItem *item) { - App::regDocumentItem(_data, item); +void HistorySticker::attachToParent() { + App::regDocumentItem(_data, _parent); } -void HistorySticker::detachFromItem(HistoryItem *item) { - App::unregDocumentItem(_data, item); +void HistorySticker::detachFromParent() { + App::unregDocumentItem(_data, _parent); } -void HistorySticker::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { +void HistorySticker::updateSentMedia(const MTPMessageMedia &media) { if (media.type() == mtpc_messageMediaDocument) { App::feedDocument(media.c_messageMediaDocument().vdocument, _data); if (!_data->data().isEmpty()) { @@ -4904,28 +5220,39 @@ void HistorySticker::updateFrom(const MTPMessageMedia &media, HistoryItem *paren } } -void SendMessageLink::onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton) { - Ui::showPeerHistory(peer()->id, ShowAtUnreadMsgId); - } +bool HistorySticker::needReSetInlineResultMedia(const MTPMessageMedia &media) { + return needReSetInlineResultDocument(media, _data); } -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)); - } +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 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)); } } } } -HistoryContact::HistoryContact(int32 userId, const QString &first, const QString &last, const QString &phone) : HistoryMedia() +HistoryContact::HistoryContact(HistoryItem *parent, int32 userId, const QString &first, const QString &last, const QString &phone) : HistoryMedia(parent) , _userId(userId) , _contact(0) , _phonew(0) @@ -4938,7 +5265,7 @@ HistoryContact::HistoryContact(int32 userId, const QString &first, const QString _phonew = st::normalFont->width(_phone); } -void HistoryContact::initDimensions(const HistoryItem *parent) { +void HistoryContact::initDimensions() { _maxw = st::msgFileMinWidth; _contact = _userId ? App::userLoaded(_userId) : 0; @@ -4946,10 +5273,10 @@ void HistoryContact::initDimensions(const HistoryItem *parent) { _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); @@ -4962,7 +5289,7 @@ void HistoryContact::initDimensions(const HistoryItem *parent) { } else { tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); tright = st::msgFileThumbPadding.left(); - _maxw = qMax(_maxw, tleft + _phonew + parent->skipBlockWidth() + st::msgPadding.right()); + _maxw = qMax(_maxw, tleft + _phonew + _parent->skipBlockWidth() + st::msgPadding.right()); } _maxw = qMax(tleft + _name.maxWidth() + tright, _maxw); @@ -4976,11 +5303,12 @@ void HistoryContact::initDimensions(const HistoryItem *parent) { _height = _minh; } -void HistoryContact::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { +void HistoryContact::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; - bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; + bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; + bool selected = (selection == FullSelection); if (width >= _maxw) { width = _maxw; @@ -5004,7 +5332,7 @@ void HistoryContact::draw(Painter &p, const HistoryItem *parent, const QRect &r, 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); @@ -5015,7 +5343,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)->pixCircled(st::msgFileSize, st::msgFileSize)); + p.drawPixmap(inner.topLeft(), userDefPhoto(qAbs(_parent->id) % UserColorsCount)->pixCircled(st::msgFileSize, st::msgFileSize)); } int32 namewidth = width - nameleft - nameright; @@ -5029,22 +5357,24 @@ 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(), isPost = parent->isPost(), outbg = out && !isPost; +HistoryTextState HistoryContact::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; if (_userId) { nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); linktop = st::msgFileThumbLinkTop; if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, _width).contains(x, y)) { - lnk = _linkl; - return; + result.link = _linkl; + return result; } } if (x >= 0 && y >= 0 && x < _width && y < _height && _contact) { - lnk = _contact->lnk; - return; + result.link = _contact->openLink(); + return result; } + return result; } const QString HistoryContact::inDialogsText() const { @@ -5055,24 +5385,24 @@ const QString HistoryContact::inHistoryText() const { return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" : ") + _name.original() + qsl(", ") + _phone + qsl(" ]"); } -void HistoryContact::attachToItem(HistoryItem *item) { +void HistoryContact::attachToParent() { if (_userId) { - App::regSharedContactItem(_userId, item); + App::regSharedContactItem(_userId, _parent); } } -void HistoryContact::detachFromItem(HistoryItem *item) { +void HistoryContact::detachFromParent() { if (_userId) { - App::unregSharedContactItem(_userId, item); + App::unregSharedContactItem(_userId, _parent); } } -void HistoryContact::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { +void HistoryContact::updateSentMedia(const MTPMessageMedia &media) { if (media.type() == mtpc_messageMediaContact) { if (_userId != media.c_messageMediaContact().vuser_id.v) { - detachFromItem(parent); + detachFromParent(); _userId = media.c_messageMediaContact().vuser_id.v; - attachToItem(parent); + attachToParent(); } } } @@ -5105,10 +5435,10 @@ namespace { int32 _lineHeight = 0; } -HistoryWebPage::HistoryWebPage(WebPageData *data) : HistoryMedia() +HistoryWebPage::HistoryWebPage(HistoryItem *parent, WebPageData *data) : HistoryMedia(parent) , _data(data) , _openl(0) -, _attach(0) +, _attach(nullptr) , _asArticle(false) , _title(st::msgMinWidth - st::webPageLeft) , _description(st::msgMinWidth - st::webPageLeft) @@ -5118,10 +5448,10 @@ HistoryWebPage::HistoryWebPage(WebPageData *data) : HistoryMedia() , _pixh(0) { } -HistoryWebPage::HistoryWebPage(const HistoryWebPage &other) : HistoryMedia() +HistoryWebPage::HistoryWebPage(HistoryItem *parent, const HistoryWebPage &other) : HistoryMedia(parent) , _data(other._data) , _openl(0) -, _attach(other._attach ? other._attach->clone() : 0) +, _attach(other._attach ? other._attach->clone(parent) : nullptr) , _asArticle(other._asArticle) , _title(other._title) , _description(other._description) @@ -5131,21 +5461,21 @@ HistoryWebPage::HistoryWebPage(const HistoryWebPage &other) : HistoryMedia() , _pixh(other._pixh) { } -void HistoryWebPage::initDimensions(const HistoryItem *parent) { +void HistoryWebPage::initDimensions() { if (_data->pendingTill) { _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); if (!_data->description.isEmpty() && title.isEmpty() && _data->siteName.isEmpty() && !_data->url.isEmpty()) { _data->siteName = siteNameFromUrl(_data->url); } - if (!_data->doc && _data->photo && _data->type != WebPagePhoto && _data->type != WebPageVideo) { + if (!_data->document && _data->photo && _data->type != WebPagePhoto && _data->type != WebPageVideo) { if (_data->type == WebPageProfile) { _asArticle = true; } else if (_data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) { @@ -5162,18 +5492,18 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { // init attach if (!_asArticle && !_attach) { - if (_data->doc) { - if (_data->doc->sticker()) { - _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); + if (_data->document) { + if (_data->document->sticker()) { + _attach = new HistorySticker(_parent, _data->document); + } else if (_data->document->isAnimation()) { + _attach = new HistoryGif(_parent, _data->document, QString()); + } else if (_data->document->isVideo()) { + _attach = new HistoryVideo(_parent, _data->document, QString()); } else { - _attach = new HistoryDocument(_data->doc, QString(), parent); + _attach = new HistoryDocument(_parent, _data->document, QString()); } } else if (_data->photo) { - _attach = new HistoryPhoto(_data->photo, QString(), parent); + _attach = new HistoryPhoto(_parent, _data->photo, QString()); } } @@ -5184,7 +5514,7 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { _data->description = QString(); } else { if (!_asArticle && !_attach) { - text += parent->skipBlock(); + text += _parent->skipBlock(); } const TextParseOptions *opts = &_webpageDescriptionOptions; if (_data->siteName == qstr("Twitter")) { @@ -5205,7 +5535,7 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { } } else { if (!_asArticle && !_attach && _description.isEmpty()) { - title += parent->skipBlock(); + title += _parent->skipBlock(); } _title.setText(st::webPageTitleFont, title, _webpageTitleOptions); } @@ -5216,7 +5546,7 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { // init dimensions int32 l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right(); - int32 skipBlockWidth = parent->skipBlockWidth(); + int32 skipBlockWidth = _parent->skipBlockWidth(); _maxw = skipBlockWidth; _minh = 0; @@ -5232,7 +5562,7 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { if (_siteNameWidth) { if (_title.isEmpty() && _description.isEmpty()) { - _maxw = qMax(_maxw, int32(_siteNameWidth + parent->skipBlockWidth())); + _maxw = qMax(_maxw, int32(_siteNameWidth + _parent->skipBlockWidth())); } else { _maxw = qMax(_maxw, int32(_siteNameWidth + articlePhotoMaxWidth)); } @@ -5248,7 +5578,7 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { } if (_attach) { if (_minh) _minh += st::webPagePhotoSkip; - _attach->initDimensions(parent); + _attach->initDimensions(); QMargins bubble(_attach->bubbleMargins()); _maxw = qMax(_maxw, int32(_attach->maxWidth() - bubble.left() - bubble.top() + (_attach->customInfoLayout() ? skipBlockWidth : 0))); _minh += _attach->minHeight() - bubble.top() - bubble.bottom(); @@ -5260,12 +5590,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); // hack // _minh += st::msgDateFont->height; } } -int32 HistoryWebPage::resize(int32 width, const HistoryItem *parent) { +int HistoryWebPage::resizeGetHeight(int width) { if (_data->pendingTill) { _width = width; _height = _minh; @@ -5342,9 +5672,9 @@ 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()); _height += _attach->height() - bubble.top() - bubble.bottom(); - if (_attach->customInfoLayout() && _attach->currentWidth() + parent->skipBlockWidth() > width + bubble.left() + bubble.right()) { + if (_attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right()) { _height += st::msgDateFont->height; } } @@ -5354,11 +5684,12 @@ int32 HistoryWebPage::resize(int32 width, const HistoryItem *parent) { return _height; } -void HistoryWebPage::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { +void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 skipx = 0, skipy = 0, width = _width, height = _height; - bool out = parent->out(), isPost = parent->isPost(), outbg = out && !isPost; + bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; + bool selected = (selection == FullSelection); 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)); @@ -5367,7 +5698,7 @@ void HistoryWebPage::draw(Painter &p, const HistoryItem *parent, const QRect &r, int32 lshift = st::msgPadding.left() + st::webPageLeft, rshift = st::msgPadding.right(), bshift = st::msgPadding.bottom(); width -= lshift + rshift; QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - if (_asArticle || (_attach && _attach->customInfoLayout() && _attach->currentWidth() + parent->skipBlockWidth() > width + bubble.left() + bubble.right())) { + if (_asArticle || (_attach && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right())) { bshift += st::msgDateFont->height; } @@ -5408,18 +5739,18 @@ void HistoryWebPage::draw(Painter &p, const HistoryItem *parent, const QRect &r, p.setPen(st::black); int32 endskip = 0; if (_title.hasSkipBlock()) { - endskip = parent->skipBlockWidth(); + endskip = _parent->skipBlockWidth(); } - _title.drawLeftElided(p, lshift, tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip); + _title.drawLeftElided(p, lshift, tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip, false, selection); tshift += _titleLines * _lineHeight; } if (_descriptionLines) { p.setPen(st::black); int32 endskip = 0; if (_description.hasSkipBlock()) { - endskip = parent->skipBlockWidth(); + endskip = _parent->skipBlockWidth(); } - _description.drawLeftElided(p, lshift, tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip); + _description.drawLeftElided(p, lshift, tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); tshift += _descriptionLines * _lineHeight; } if (_attach) { @@ -5431,7 +5762,8 @@ void HistoryWebPage::draw(Painter &p, const HistoryItem *parent, const QRect &r, p.save(); p.translate(attachLeft, attachTop); - _attach->draw(p, parent, r.translated(-attachLeft, -attachTop), selected, ms); + auto attachSelection = selected ? FullSelection : TextSelection{ 0, 0 }; + _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); int32 pixwidth = _attach->currentWidth(), pixheight = _attach->height(); if (_data->type == WebPageVideo) { @@ -5458,81 +5790,109 @@ 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 { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int32 skipx = 0, skipy = 0, width = _width, height = _height; int32 lshift = st::msgPadding.left() + st::webPageLeft, rshift = st::msgPadding.right(), bshift = st::msgPadding.bottom(); width -= lshift + rshift; QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - if (_asArticle || (_attach && _attach->customInfoLayout() && _attach->currentWidth() + parent->skipBlockWidth() > width + bubble.left() + bubble.right())) { + if (_asArticle || (_attach && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right())) { bshift += st::msgDateFont->height; } + bool inThumb = false; if (_asArticle) { int32 pw = qMax(_pixw, int16(_lineHeight)); if (rtlrect(lshift + width - pw, 0, pw, _pixh, _width).contains(x, y)) { - lnk = _openl; - return; + inThumb = true; } width -= pw + st::webPagePhotoDelta; } - int32 tshift = 0; + int tshift = 0, symbolAdd = 0; if (_siteNameWidth) { tshift += _lineHeight; } if (_titleLines) { + if (y >= tshift && y < tshift + _titleLines * _lineHeight) { + Text::StateRequestElided titleRequest = request.forText(); + titleRequest.lines = _titleLines; + result = _title.getStateElidedLeft(x - lshift, y - tshift, width, _width, titleRequest); + } else if (y >= tshift + _titleLines * _lineHeight) { + symbolAdd += _title.length(); + } tshift += _titleLines * _lineHeight; } if (_descriptionLines) { if (y >= tshift && y < tshift + _descriptionLines * _lineHeight) { - bool inText = false; - _description.getStateLeft(lnk, inText, x - lshift, y - tshift, width, _width); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; + Text::StateRequestElided descriptionRequest = request.forText(); + descriptionRequest.lines = _descriptionLines; + result = _description.getStateElidedLeft(x - lshift, y - tshift, width, _width, descriptionRequest); + } else if (y >= tshift + _descriptionLines * _lineHeight) { + symbolAdd += _description.length(); } tshift += _descriptionLines * _lineHeight; } - if (_attach) { + if (inThumb) { + result.link = _openl; + } else if (_attach) { if (tshift) tshift += st::webPagePhotoSkip; if (x >= lshift && x < lshift + width && y >= tshift && y < _height - st::msgPadding.bottom()) { int32 attachLeft = lshift - bubble.left(), attachTop = tshift - bubble.top(); if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth(); - _attach->getState(lnk, state, x - attachLeft, y - attachTop, parent); - if (lnk && !_data->doc && _data->photo) { + result = _attach->getState(x - attachLeft, y - attachTop, request); + + if (result.link && !_data->document && _data->photo) { if (_data->type == WebPageProfile || _data->type == WebPageVideo) { - lnk = _openl; + result.link = _openl; } else if (_data->type == WebPagePhoto || _data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) { // leave photo link } else { - lnk = _openl; + result.link = _openl; } } } } + + result.symbol += symbolAdd; + return result; } -void HistoryWebPage::linkOver(HistoryItem *parent, const TextLinkPtr &lnk) { +TextSelection HistoryWebPage::adjustSelection(TextSelection selection, TextSelectType type) const { + if (!_descriptionLines || selection.to <= _title.length()) { + return _title.adjustSelection(selection, type); + } + auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); + if (selection.from >= _title.length()) { + return fromDescriptionSelection(descriptionSelection); + } + auto titleSelection = _title.adjustSelection(selection, type); + return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; +} + +void HistoryWebPage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (_attach) { - _attach->linkOver(parent, lnk); + _attach->clickHandlerActiveChanged(p, active); } } -void HistoryWebPage::linkOut(HistoryItem *parent, const TextLinkPtr &lnk) { +void HistoryWebPage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { if (_attach) { - _attach->linkOut(parent, lnk); + _attach->clickHandlerPressedChanged(p, pressed); } } -void HistoryWebPage::attachToItem(HistoryItem *item) { - App::regWebPageItem(_data, item); - if (_attach) _attach->attachToItem(item); +void HistoryWebPage::attachToParent() { + App::regWebPageItem(_data, _parent); + if (_attach) _attach->attachToParent(); } -void HistoryWebPage::detachFromItem(HistoryItem *item) { - App::unregWebPageItem(_data, item); - if (_attach) _attach->detachFromItem(item); +void HistoryWebPage::detachFromParent() { + App::unregWebPageItem(_data, _parent); + if (_attach) _attach->detachFromParent(); } const QString HistoryWebPage::inDialogsText() const { @@ -5744,29 +6104,35 @@ void LocationData::load() { manager.getData(this); } -HistoryLocation::HistoryLocation(const LocationCoords &coords, const QString &title, const QString &description) : HistoryMedia(), -_title(st::msgMinWidth), -_description(st::msgMinWidth) { +HistoryLocation::HistoryLocation(HistoryItem *parent, const LocationCoords &coords, const QString &title, const QString &description) : HistoryMedia(parent) +, _data(App::location(coords)) +, _title(st::msgMinWidth) +, _description(st::msgMinWidth) +, _link(new LocationClickHandler(coords)) { if (!title.isEmpty()) { _title.setText(st::webPageTitleFont, textClean(title), _webpageTitleOptions); } if (!description.isEmpty()) { _description.setText(st::webPageDescriptionFont, textClean(description), _webpageDescriptionOptions); } - - _link.reset(new LocationLink(coords)); - _data = App::location(coords); } -void HistoryLocation::initDimensions(const HistoryItem *parent) { - bool bubble = parent->hasBubble(); +HistoryLocation::HistoryLocation(HistoryItem *parent, const HistoryLocation &other) : HistoryMedia(parent) +, _data(other._data) +, _title(other._title) +, _description(other._description) +, _link(new LocationClickHandler(_data->coords)) { +} + +void HistoryLocation::initDimensions() { + bool bubble = _parent->hasBubble(); int32 tw = fullWidth(), th = fullHeight(); if (tw > st::maxMediaSize) { th = (st::maxMediaSize * th) / tw; tw = st::maxMediaSize; } - int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + int32 minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); _maxw = qMax(tw, int32(minWidth)); _minh = qMax(th, int32(st::minPhotoSize)); @@ -5782,15 +6148,15 @@ void HistoryLocation::initDimensions(const HistoryItem *parent) { _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_title.isEmpty() || !_description.isEmpty()) { _minh += st::webPagePhotoSkip; - if (!parent->Has() && !parent->Has()) { + if (!_parent->Has() && !_parent->Has()) { _minh += st::msgPadding.top(); } } } } -int32 HistoryLocation::resize(int32 width, const HistoryItem *parent) { - bool bubble = parent->hasBubble(); +int HistoryLocation::resizeGetHeight(int width) { + bool bubble = _parent->hasBubble(); _width = qMin(width, _maxw); if (bubble) { @@ -5808,7 +6174,7 @@ int32 HistoryLocation::resize(int32 width, const HistoryItem *parent) { } else { _width = tw; } - int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + int32 minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); _width = qMax(_width, int32(minWidth)); _height = qMax(_height, int32(st::minPhotoSize)); if (bubble) { @@ -5822,7 +6188,7 @@ int32 HistoryLocation::resize(int32 width, const HistoryItem *parent) { } if (!_title.isEmpty() || !_description.isEmpty()) { _height += st::webPagePhotoSkip; - if (!parent->Has() && !parent->Has()) { + if (!_parent->Has() && !_parent->Has()) { _height += st::msgPadding.top(); } } @@ -5830,18 +6196,19 @@ int32 HistoryLocation::resize(int32 width, const HistoryItem *parent) { return _height; } -void HistoryLocation::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { +void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection, 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(), isPost = parent->isPost(), outbg = out && !isPost; + bool bubble = _parent->hasBubble(); + bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; + bool selected = (selection == FullSelection); if (bubble) { skipx = st::mediaPadding.left(); skipy = st::mediaPadding.top(); if (!_title.isEmpty() || !_description.isEmpty()) { - if (!parent->Has() && !parent->Has()) { + if (!_parent->Has() && !_parent->Has()) { skipy += st::msgPadding.top(); } } @@ -5851,11 +6218,11 @@ void HistoryLocation::draw(Painter &p, const HistoryItem *parent, const QRect &r p.setPen(st::black); if (!_title.isEmpty()) { - _title.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 2); + _title.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 2, style::al_left, 0, -1, 0, false, selection); skipy += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); } if (!_description.isEmpty()) { - _description.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 3); + _description.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 3, style::al_left, 0, -1, 0, false, toDescriptionSelection(selection)); skipy += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); } if (!_title.isEmpty() || !_description.isEmpty()) { @@ -5888,23 +6255,25 @@ void HistoryLocation::draw(Painter &p, const HistoryItem *parent, const QRect &r App::roundRect(p, skipx, skipy, width, height, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); } - if (parent->getMedia() == this) { + if (_parent->getMedia() == this) { int32 fullRight = skipx + width, fullBottom = _height - (skipx ? st::mediaPadding.bottom() : 0); - parent->drawInfo(p, fullRight, fullBottom, skipx * 2 + width, selected, InfoDisplayOverImage); + _parent->drawInfo(p, fullRight, fullBottom, skipx * 2 + width, selected, InfoDisplayOverImage); } } -void HistoryLocation::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { - if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; +HistoryTextState HistoryLocation::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int32 skipx = 0, skipy = 0, width = _width, height = _height; - bool bubble = parent->hasBubble(); + bool bubble = _parent->hasBubble(); if (bubble) { skipx = st::mediaPadding.left(); skipy = st::mediaPadding.top(); if (!_title.isEmpty() || !_description.isEmpty()) { - if (!parent->Has() && !parent->Has()) { + if (!_parent->Has() && !_parent->Has()) { skipy += st::msgPadding.top(); } } @@ -5913,10 +6282,21 @@ void HistoryLocation::getState(TextLinkPtr &lnk, HistoryCursorState &state, int3 int32 textw = _width - st::msgPadding.left() - st::msgPadding.right(); if (!_title.isEmpty()) { - skipy += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); + auto titleh = qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); + if (y >= skipy && y < skipy + titleh) { + result = _title.getStateLeft(x - skipx - st::msgPadding.left(), y - skipy, textw, _width, request.forText()); + return result; + } + skipy += titleh; } if (!_description.isEmpty()) { - skipy += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); + auto descriptionh = qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); + if (y >= skipy && y < skipy + descriptionh) { + result = _description.getStateLeft(x - skipx - st::msgPadding.left(), y - skipy, textw, _width, request.forText()); + if (!_title.isEmpty()) result.symbol += _title.length(); + return result; + } + skipy += descriptionh; } if (!_title.isEmpty() || !_description.isEmpty()) { skipy += st::webPagePhotoSkip; @@ -5924,16 +6304,29 @@ void HistoryLocation::getState(TextLinkPtr &lnk, HistoryCursorState &state, int3 height -= skipy + st::mediaPadding.bottom(); } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height && _data) { - lnk = _link; + result.link = _link; int32 fullRight = skipx + width, fullBottom = _height - (skipx ? st::mediaPadding.bottom() : 0); - bool inDate = parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); + bool inDate = _parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); if (inDate) { - state = HistoryInDateCursorState; + result.cursor = HistoryInDateCursorState; } - return; + return result; } + return result; +} + +TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSelectType type) const { + if (_description.isEmpty() || selection.to <= _title.length()) { + return _title.adjustSelection(selection, type); + } + auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); + if (selection.from >= _title.length()) { + return fromDescriptionSelection(descriptionSelection); + } + auto titleSelection = _title.adjustSelection(selection, type); + return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; } const QString HistoryLocation::inDialogsText() const { @@ -5952,14 +6345,14 @@ int32 HistoryLocation::fullHeight() const { return st::locationSize.height(); } -void ViaInlineBotLink::onClick(Qt::MouseButton button) const { +void ViaInlineBotClickHandler::onClickImpl() const { App::insertBotCommand('@' + _bot->username); } 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 ViaInlineBotLink(_bot)); + _lnk.reset(new ViaInlineBotClickHandler(_bot)); } void HistoryMessageVia::resize(int32 availw) const { @@ -6014,7 +6407,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { textstyleSet(&st::inFwdTextStyle); _text.setText(st::msgServiceNameFont, text, opts); textstyleRestore(); - _text.setLink(1, (_originalId && _authorOriginal->isChannel()) ? TextLinkPtr(new MessageLink(_authorOriginal->id, _originalId)) : _authorOriginal->lnk); + _text.setLink(1, (_originalId && _authorOriginal->isChannel()) ? ClickHandlerPtr(new GoToMessageClickHandler(_authorOriginal->id, _originalId)) : _authorOriginal->openLink()); if (via) { _text.setLink(2, via->_lnk); } @@ -6038,10 +6431,10 @@ bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) { updateName(); - replyToLnk = TextLinkPtr(new MessageLink(replyToMsg->history()->peer->id, replyToMsg->id)); + replyToLnk.reset(new GoToMessageClickHandler(replyToMsg->history()->peer->id, replyToMsg->id)); if (!replyToMsg->Has()) { if (UserData *bot = replyToMsg->viaBot()) { - _replyToVia = new HistoryMessageVia(0); + _replyToVia.reset(new HistoryMessageVia()); _replyToVia->create(peerToUser(bot->id)); } } @@ -6055,7 +6448,6 @@ bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) { } void HistoryMessageReply::clearData(HistoryMessage *holder) { - delete _replyToVia; _replyToVia = nullptr; if (replyToMsg) { App::historyUnregDependency(holder, replyToMsg); @@ -6162,22 +6554,84 @@ void HistoryMessageReply::paint(Painter &p, const HistoryItem *holder, int x, in } } +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 { + using Button = HistoryMessageReplyMarkup::Button; + style::sprite sprite; + switch (type) { + case Button::Url: sprite = st::msgBotKbUrlIcon; break; +// case Button::RequestPhone: sprite = st::msgBotKbRequestPhoneIcon; break; +// case Button::RequestLocation: sprite = st::msgBotKbRequestLocationIcon; break; + case Button::SwitchInline: sprite = st::msgBotKbSwitchPmIcon; 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 { + using Button = HistoryMessageReplyMarkup::Button; + int result = 2 * buttonPadding(), iconWidth = 0; + switch (type) { + case Button::Url: iconWidth = st::msgBotKbUrlIcon.pxWidth(); break; + //case Button::RequestPhone: iconWidth = st::msgBotKbRequestPhoneIcon.pxWidth(); break; + //case Button::RequestLocation: iconWidth = st::msgBotKbRequestLocationIcon.pxWidth(); break; + case Button::SwitchInline: iconWidth = st::msgBotKbSwitchPmIcon.pxWidth(); break; + case Button::Callback: iconWidth = st::msgInvSendingImg.pxWidth(); break; + } + if (iconWidth > 0) { + result = std::max(result, 2 * iconWidth + 4 * 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) { - PeerId authorOriginalId = 0, fromOriginalId = 0; - MsgId originalId = 0; + CreateConfig config; + if (msg.has_fwd_from() && msg.vfwd_from.type() == mtpc_messageFwdHeader) { - const MTPDmessageFwdHeader &f(msg.vfwd_from.c_messageFwdHeader()); + const auto &f(msg.vfwd_from.c_messageFwdHeader()); if (f.has_from_id() || f.has_channel_id()) { - authorOriginalId = f.has_channel_id() ? peerFromChannel(f.vchannel_id) : peerFromUser(f.vfrom_id); - fromOriginalId = f.has_from_id() ? peerFromUser(f.vfrom_id) : peerFromChannel(f.vchannel_id); - if (f.has_channel_post()) originalId = f.vchannel_post.v; + 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; } } - MsgId replyTo = msg.has_reply_to_msg_id() ? msg.vreply_to_msg_id.v : 0; - int32 viaBotId = msg.has_via_bot_id() ? msg.vvia_bot_id.v : 0; - int views = msg.has_views() ? msg.vviews.v : -1; - createInterfaces(replyTo, viaBotId, views, authorOriginalId, fromOriginalId, originalId); + 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))); initMedia(msg.has_media() ? (&msg.vmedia) : 0, text); @@ -6186,86 +6640,114 @@ HistoryMessage::HistoryMessage(History *history, const MTPDmessage &msg) 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; + + config.authorIdOriginal = fwd->authorOriginal()->id; + config.fromIdOriginal = fwd->fromOriginal()->id; + if (fwd->authorOriginal()->isChannel()) { + config.originalId = fwd->id; + } UserData *fwdViaBot = fwd->viaBot(); - int32 viaBotId = fwdViaBot ? peerToUser(fwdViaBot->id) : 0; - int fwdViewsCount = fwd->viewsCount(), views = (fwdViewsCount > 0) ? fwdViewsCount : (isPost() ? 1 : -1); - MsgId replyTo = 0; - createInterfaces(replyTo, viaBotId, views, fwd->authorOriginal()->id, fwd->fromOriginal()->id, fwd->authorOriginal()->isChannel() ? fwd->id : 0); + 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 = mediaOriginal->clone(); - _media->attachToItem(this); + _media.reset(mediaOriginal->clone(this)); } 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) { - createInterfacesHelper(flags, replyTo, viaBotId); + createComponentsHelper(flags, replyTo, viaBotId, MTPnullMarkup); setText(msg, entities); } -HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption) +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) { - createInterfacesHelper(flags, replyTo, viaBotId); + createComponentsHelper(flags, replyTo, viaBotId, markup); initMediaFromDocument(doc, caption); setText(QString(), EntitiesInText()); } -HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption) +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) { - createInterfacesHelper(flags, replyTo, viaBotId); + createComponentsHelper(flags, replyTo, viaBotId, markup); - _media = new HistoryPhoto(photo, caption, this); - _media->attachToItem(this); + _media.reset(new HistoryPhoto(this, photo, caption)); setText(QString(), EntitiesInText()); } -void HistoryMessage::createInterfacesHelper(MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId) { - if (!(flags & MTPDmessage::Flag::f_via_bot_id)) viaBotId = 0; - if (!(flags & MTPDmessage::Flag::f_reply_to_msg_id)) replyTo = 0; - createInterfaces(replyTo, viaBotId, isPost() ? 1 : -1); +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::createInterfaces(MsgId replyTo, int32 viaBotId, int32 viewsCount, const PeerId &authorIdOriginal, const PeerId &fromIdOriginal, MsgId originalId) { +void HistoryMessage::createComponents(const CreateConfig &config) { uint64 mask = 0; - if (replyTo) { + if (config.replyTo) { mask |= HistoryMessageReply::Bit(); } - if (viaBotId) { + if (config.viaBotId) { mask |= HistoryMessageVia::Bit(); } - if (viewsCount >= 0) { + if (config.viewsCount >= 0) { mask |= HistoryMessageViews::Bit(); } if (isPost() && _from->isUser()) { mask |= HistoryMessageSigned::Bit(); } - if (authorIdOriginal && fromIdOriginal) { + if (config.authorIdOriginal && config.fromIdOriginal) { mask |= HistoryMessageForwarded::Bit(); } - UpdateComponents(mask); - if (auto *reply = Get()) { - reply->replyToMsgId = replyTo; - if (!reply->updateData(this) && App::api()) { - App::api()->requestMessageData(history()->peer->asChannel(), replyTo, new HistoryDependentItemCallback(fullId())); + 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(); } } - if (auto *via = Get()) { - via->create(viaBotId); + 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 *views = Get()) { - views->_views = viewsCount; + if (auto via = Get()) { + via->create(config.viaBotId); } - if (auto *msgsigned = Get()) { + 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(authorIdOriginal); - fwd->_fromOriginal = App::peer(fromIdOriginal); - fwd->_originalId = originalId; + 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); + if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button) { + _flags |= MTPDmessage_ClientFlag::f_has_switch_inline_button; + } } initTime(); } @@ -6290,13 +6772,13 @@ QString formatViewsCount(int32 views) { } void HistoryMessage::initTime() { - if (auto *msgsigned = Get()) { + if (auto msgsigned = Get()) { _timeWidth = msgsigned->maxWidth(); } else { _timeText = date.toString(cTimeFormat()); _timeWidth = st::msgDateFont->width(_timeText); } - if (auto *views = Get()) { + if (auto views = Get()) { views->_viewsText = (views->_views >= 0) ? formatViewsCount(views->_views) : QString(); views->_viewsWidth = views->_viewsText.isEmpty() ? 0 : st::msgDateFont->width(views->_viewsText); } @@ -6305,62 +6787,58 @@ void HistoryMessage::initTime() { 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)); + const auto &d(media->c_messageMediaContact()); + _media.reset(new HistoryContact(this, 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); + const auto &point(media->c_messageMediaGeo().vgeo); if (point.type() == mtpc_geoPoint) { - const MTPDgeoPoint &d(point.c_geoPoint()); - _media = new HistoryLocation(LocationCoords(d.vlat.v, d.vlong.v)); + _media.reset(new HistoryLocation(this, LocationCoords(point.c_geoPoint()))); } } break; case mtpc_messageMediaVenue: { - const MTPDmessageMediaVenue &d(media->c_messageMediaVenue()); + const auto &d(media->c_messageMediaVenue()); if (d.vgeo.type() == mtpc_geoPoint) { - const MTPDgeoPoint &g(d.vgeo.c_geoPoint()); - _media = new HistoryLocation(LocationCoords(g.vlat.v, g.vlong.v), qs(d.vtitle), qs(d.vaddress)); + _media.reset(new HistoryLocation(this, LocationCoords(d.vgeo.c_geoPoint()), qs(d.vtitle), qs(d.vaddress))); } } break; case mtpc_messageMediaPhoto: { - const MTPDmessageMediaPhoto &photo(media->c_messageMediaPhoto()); + const auto &photo(media->c_messageMediaPhoto()); if (photo.vphoto.type() == mtpc_photo) { - _media = new HistoryPhoto(App::feedPhoto(photo.vphoto.c_photo()), qs(photo.vcaption), this); + _media.reset(new HistoryPhoto(this, App::feedPhoto(photo.vphoto.c_photo()), qs(photo.vcaption))); } } break; case mtpc_messageMediaDocument: { - const MTPDocument &document(media->c_messageMediaDocument().vdocument); + const auto &document(media->c_messageMediaDocument().vdocument); if (document.type() == mtpc_document) { return initMediaFromDocument(App::feedDocument(document), qs(media->c_messageMediaDocument().vcaption)); } } break; case mtpc_messageMediaWebPage: { - const MTPWebPage &d(media->c_messageMediaWebPage().vwebpage); + const auto &d(media->c_messageMediaWebPage().vwebpage); switch (d.type()) { case mtpc_webPageEmpty: break; case mtpc_webPagePending: { - _media = new HistoryWebPage(App::feedWebPage(d.c_webPagePending())); + _media.reset(new HistoryWebPage(this, App::feedWebPage(d.c_webPagePending()))); } break; case mtpc_webPage: { - _media = new HistoryWebPage(App::feedWebPage(d.c_webPage())); + _media.reset(new HistoryWebPage(this, App::feedWebPage(d.c_webPage()))); } break; } } break; }; - if (_media) _media->attachToItem(this); } void HistoryMessage::initMediaFromDocument(DocumentData *doc, const QString &caption) { if (doc->sticker()) { - _media = new HistorySticker(doc); + _media.reset(new HistorySticker(this, doc)); } else if (doc->isAnimation()) { - _media = new HistoryGif(doc, caption, this); + _media.reset(new HistoryGif(this, doc, caption)); } else if (doc->isVideo()) { - _media = new HistoryVideo(doc, caption, this); + _media.reset(new HistoryVideo(this, doc, caption)); } else { - _media = new HistoryDocument(doc, caption, this); + _media.reset(new HistoryDocument(this, doc, caption)); } - _media->attachToItem(this); } int32 HistoryMessage::plainMaxWidth() const { @@ -6368,7 +6846,6 @@ int32 HistoryMessage::plainMaxWidth() const { } void HistoryMessage::initDimensions() { - auto *reply = Get(); if (drawBubble()) { auto fwd = Get(); auto via = Get(); @@ -6377,7 +6854,7 @@ void HistoryMessage::initDimensions() { } if (_media) { - _media->initDimensions(this); + _media->initDimensions(); if (_media->isDisplayed()) { if (_text.hasSkipBlock()) { _text.removeSkipBlock(); @@ -6423,13 +6900,13 @@ void HistoryMessage::initDimensions() { } } } else { - _media->initDimensions(this); + _media->initDimensions(); _maxw = _media->maxWidth(); _minh = _media->minHeight(); } - if (reply) { + if (auto reply = Get()) { reply->updateName(); - if (!_media) { + 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; @@ -6437,6 +6914,17 @@ void HistoryMessage::initDimensions() { if (replyw > _maxw) _maxw = replyw; } } + if (HistoryMessageReplyMarkup *markup = inlineReplyMarkup()) { + if (!markup->inlineKeyboard) { + markup->inlineKeyboard.reset(new ReplyKeyboard(this, std_::make_unique(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 { @@ -6464,17 +6952,70 @@ void HistoryMessage::countPositionAndSize(int32 &left, int32 &width) const { void HistoryMessage::fromNameUpdated(int32 width) const { _authorNameVersion = author()->nameVersion; if (!Has()) { - if (auto *via = Get()) { + if (auto via = Get()) { via->resize(width - st::msgPadding.left() - st::msgPadding.right() - author()->nameText.maxWidth() - st::msgServiceFont->spacew); } } } +void HistoryMessage::applyEdition(const MTPDmessage &message) { + int keyboardTop = -1; + if (!pendingResize()) { + if (auto keyboard = inlineReplyKeyboard()) { + int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); + keyboardTop = _height - h + st::msgBotKbButton.margin - marginBottom(); + } + } + + 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; + } + + if (keyboardTop >= 0) { + if (auto keyboard = Get()) { + keyboard->oldTop = keyboardTop; + } + } +} + +void HistoryMessage::updateMedia(const MTPMessageMedia *media) { + if (_flags & MTPDmessage_ClientFlag::f_from_inline_bot) { + bool needReSet = true; + if (media && _media) { + needReSet = _media->needReSetInlineResultMedia(*media); + } + if (needReSet) { + setMedia(media); + } + _flags &= ~MTPDmessage_ClientFlag::f_from_inline_bot; + } else if (media && _media && _media->type() != MediaTypeWebPage) { + _media->updateSentMedia(*media); + } else { + setMedia(media); + } + 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)) { @@ -6491,7 +7032,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); @@ -6502,25 +7043,23 @@ void HistoryMessage::eraseFromOverview() { } } -QString HistoryMessage::selectedText(uint32 selection) const { +QString HistoryMessage::selectedText(TextSelection selection) const { QString result; if (_media && selection == FullSelection) { - QString text = _text.original(0, 0xFFFF, Text::ExpandLinksAll), mediaText = _media->inHistoryText(); + QString text = _text.original(AllTextSelection, Text::ExpandLinksAll), mediaText = _media->inHistoryText(); 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); + result = _text.original((selection == FullSelection) ? AllTextSelection : selection, Text::ExpandLinksAll); } - if (auto *fwd = Get()) { + if (auto fwd = Get()) { if (selection == FullSelection) { - QString fwdinfo = fwd->_text.original(0, 0xFFFF, Text::ExpandLinksAll), wrapped; + QString fwdinfo = fwd->_text.original(AllTextSelection, 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 (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()); @@ -6532,23 +7071,20 @@ QString HistoryMessage::selectedText(uint32 selection) const { } QString HistoryMessage::inDialogsText() const { - return emptyText() ? (_media ? _media->inDialogsText() : QString()) : _text.original(0, 0xFFFF, Text::ExpandLinksNone); + return emptyText() ? (_media ? _media->inDialogsText() : QString()) : _text.original(AllTextSelection, Text::ExpandLinksNone); } -HistoryMedia *HistoryMessage::getMedia(bool inOverview) const { - return _media; +HistoryMedia *HistoryMessage::getMedia() const { + return _media.data(); } void HistoryMessage::setMedia(const MTPMessageMedia *media) { - if ((!_media || _media->isImageLink()) && (!media || media->type() == mtpc_messageMediaEmpty)) return; + if (!_media && (!media || media->type() == mtpc_messageMediaEmpty)) return; bool mediaWasDisplayed = false; if (_media) { mediaWasDisplayed = _media->isDisplayed(); - - _media->detachFromItem(this); - delete _media; - _media = nullptr; + _media.clear(); } QString t; initMedia(media, t); @@ -6582,6 +7118,50 @@ void HistoryMessage::setText(const QString &text, const EntitiesInText &entities _textHeight = 0; } +void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) { + if (!markup) { + if (_flags & MTPDmessage::Flag::f_reply_markup) { + _flags &= ~MTPDmessage::Flag::f_reply_markup; + if (Has()) { + RemoveComponents(HistoryMessageReplyMarkup::Bit()); + } + setPendingInitDimensions(); + Notify::replyMarkupUpdated(this); + } + 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) { + bool changed = false; + if (Has()) { + RemoveComponents(HistoryMessageReplyMarkup::Bit()); + changed = true; + } + if (!(_flags & MTPDmessage::Flag::f_reply_markup)) { + _flags |= MTPDmessage::Flag::f_reply_markup; + changed = true; + } + if (changed) { + setPendingInitDimensions(); + + Notify::replyMarkupUpdated(this); + } + } else { + if (!(_flags & MTPDmessage::Flag::f_reply_markup)) { + _flags |= MTPDmessage::Flag::f_reply_markup; + } + if (!Has()) { + AddComponents(HistoryMessageReplyMarkup::Bit()); + } + Get()->create(*markup); + setPendingInitDimensions(); + + Notify::replyMarkupUpdated(this); + } +} + QString HistoryMessage::originalText() const { return emptyText() ? QString() : _text.original(); } @@ -6597,18 +7177,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() && !isPost(), 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; } @@ -6620,10 +7206,13 @@ 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(); - if (auto *msgsigned = Get()) { + if (auto msgsigned = Get()) { msgsigned->_signature.drawElided(p, dateX, dateY, _timeWidth); } else { p.drawText(dateX, dateY + st::msgDateFont->ascent, _timeText); @@ -6631,46 +7220,46 @@ void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, int32 width QPoint iconPos; const QRect *iconRect = 0; - if (auto *views = Get()) { + if (auto views = Get()) { iconPos = QPoint(infoRight - infoW + st::msgViewsPos.x(), infoBottom - st::msgViewsImg.pxHeight() + st::msgViewsPos.y()); if (id > 0) { if (outbg) { - iconRect = &(overimg ? st::msgInvViewsImg : (selected ? st::msgSelectOutViewsImg : st::msgOutViewsImg)); + 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, views->_viewsText); } else { iconPos.setX(iconPos.x() + st::msgDateViewsSpace + views->_viewsWidth); if (outbg) { - iconRect = &(overimg ? st::msgInvSendingViewsImg : st::msgSendingOutViewsImg); + 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 (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) { - auto *views = Get(); + auto views = Get(); if (!views || views->_views == count || (count >= 0 && views->_views > count)) return; int32 was = views->_viewsWidth; @@ -6704,7 +7293,7 @@ void HistoryMessage::setId(MsgId newId) { } } -void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { +void HistoryMessage::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { bool outbg = out() && !isPost(), bubble = drawBubble(), selected = (selection == FullSelection); int left = 0, width = 0, height = _height; @@ -6712,15 +7301,19 @@ void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 m if (width < 1) return; int dateh = 0, unreadbarh = 0; - if (auto *date = Get()) { + if (auto date = Get()) { dateh = date->height(); - date->paint(p, 0, _history->width); + if (r.intersects(QRect(0, 0, _history->width, dateh))) { + date->paint(p, 0, _history->width); + } } - if (auto *unreadbar = Get()) { + if (auto unreadbar = Get()) { unreadbarh = unreadbar->height(); - p.translate(0, dateh); - unreadbar->paint(p, 0, _history->width); - p.translate(0, -dateh); + if (r.intersects(QRect(0, dateh, _history->width, unreadbarh))) { + p.translate(0, dateh); + unreadbar->paint(p, 0, _history->width); + p.translate(0, -dateh); + } } uint64 animms = App::main() ? App::main()->animActiveTimeStart(this) : 0; @@ -6741,20 +7334,23 @@ void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 m textstyleSet(&(outbg ? st::outTextStyle : st::inTextStyle)); - //if (displayFromPhoto()) { - // int photoleft = left + ((outbg && !Adaptive::Wide()) ? (width + (st::msgPhotoSkip - st::msgPhotoSize)) : (-st::msgPhotoSkip)); - // int phototop = marginTop(); - // author()->paintUserpic(p, st::msgPhotoSize, photoleft, phototop); - //} + 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); + } - auto *reply = Get(); + auto reply = Get(); if (reply) { reply->checkNameUpdate(); } if (bubble) { - auto *fwd = Get(); - auto *via = Get(); + auto fwd = Get(); + auto via = Get(); if (displayFromName() && author()->nameVersion > _authorNameVersion) { fromNameUpdated(width); } @@ -6790,14 +7386,12 @@ void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 m 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); + _text.draw(p, trect.x(), trect.y(), trect.width(), style::al_left, 0, -1, selection); if (_media && _media->isDisplayed()) { int32 top = height - marginBottom() - _media->height(); p.translate(left, top); - _media->draw(p, this, r.translated(-left, -top), selected, ms); + _media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms); 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); @@ -6808,7 +7402,7 @@ void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 m } else { int32 top = marginTop(); p.translate(left, top); - _media->draw(p, this, r.translated(-left, -top), selected, ms); + _media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms); p.translate(-left, -top); } @@ -6822,7 +7416,7 @@ void HistoryMessage::paintForwardedInfo(Painter &p, QRect &trect, bool selected) p.setPen(selected ? (hasOutLayout() ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (hasOutLayout() ? st::msgOutServiceFg : st::msgInServiceFg)); p.setFont(serviceFont); - auto *fwd = Get(); + 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); @@ -6833,7 +7427,7 @@ void HistoryMessage::paintForwardedInfo(Painter &p, QRect &trect, bool selected) } void HistoryMessage::paintReplyInfo(Painter &p, QRect &trect, bool selected) const { - if (auto *reply = Get()) { + if (auto reply = Get()) { int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); HistoryMessageReply::PaintFlags flags = HistoryMessageReply::PaintInBubble; @@ -6848,7 +7442,7 @@ void HistoryMessage::paintReplyInfo(Painter &p, QRect &trect, bool selected) con void HistoryMessage::paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const { if (!displayFromName() && !Has()) { - if (auto *via = Get()) { + 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); @@ -6858,7 +7452,7 @@ void HistoryMessage::paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) } void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) { - if (auto *reply = Get()) { + if (auto reply = Get()) { reply->itemRemoved(this, dependency); } } @@ -6869,6 +7463,27 @@ void HistoryMessage::destroy() { } int HistoryMessage::resizeGetHeight_(int width) { + int result = performResizeGetHeight(width); + + auto keyboard = inlineReplyKeyboard(); + if (auto markup = Get()) { + int oldTop = markup->oldTop; + if (oldTop >= 0) { + markup->oldTop = -1; + if (keyboard) { + int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); + int keyboardTop = _height - h + st::msgBotKbButton.margin - marginBottom(); + if (keyboardTop != oldTop) { + Notify::inlineKeyboardMoved(this, oldTop, keyboardTop); + } + } + } + } + + return result; +} + +int HistoryMessage::performResizeGetHeight(int width) { if (width < st::msgMinWidth) return _height; width -= st::msgMargin.left() + st::msgMargin.right(); @@ -6878,14 +7493,14 @@ int HistoryMessage::resizeGetHeight_(int width) { width = st::msgMaxWidth; } if (drawBubble()) { - auto *fwd = Get(); - auto *reply = Get(); - auto *via = Get(); + 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); } else { if (_text.isEmpty()) { _height = 0; @@ -6899,7 +7514,7 @@ int HistoryMessage::resizeGetHeight_(int width) { } _height = st::msgPadding.top() + _textHeight + st::msgPadding.bottom(); } - if (media) _height += _media->resize(width, this); + if (media) _height += _media->resizeGetHeight(width); } if (displayFromName()) { @@ -6943,13 +7558,22 @@ int HistoryMessage::resizeGetHeight_(int width) { reply->resize(width - st::msgPadding.left() - st::msgPadding.right()); } } else { - _height = _media->resize(width, this); + _height = _media->resizeGetHeight(width); } + if (auto 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 { +bool HistoryMessage::hasPoint(int x, int y) const { int left = 0, width = 0, height = _height; countPositionAndSize(left, width); if (width < 1) return false; @@ -6959,11 +7583,11 @@ bool HistoryMessage::hasPoint(int32 x, int32 y) const { QRect r(left, top, width, height - top - marginBottom()); return r.contains(x, y); } else { - return _media->hasPoint(x - left, y - marginTop(), this); + return _media->hasPoint(x - left, y - marginTop()); } } -bool HistoryMessage::pointInTime(int32 right, int32 bottom, int32 x, int32 y, InfoDisplayType type) const { +bool HistoryMessage::pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) const { int32 infoRight = right, infoBottom = bottom; switch (type) { case InfoDisplayDefault: @@ -6980,26 +7604,24 @@ 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 { - lnk = TextLinkPtr(); - state = HistoryDefaultCursorState; +HistoryTextState HistoryMessage::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; int left = 0, width = 0, height = _height; countPositionAndSize(left, width); - //if (displayFromPhoto()) { - // int32 photoleft = left + ((!isPost() && out() && !Adaptive::Wide()) ? (width + (st::msgPhotoSkip - st::msgPhotoSize)) : (-st::msgPhotoSkip)); - // if (x >= photoleft && x < photoleft + st::msgPhotoSize && y >= marginTop() && y < height - marginBottom()) { - // lnk = author()->lnk; - // return; - // } - //} - if (width < 1) return; + if (width < 1) return result; + + auto keyboard = inlineReplyKeyboard(); + if (keyboard) { + int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); + height -= h; + } if (drawBubble()) { - auto *fwd = Get(); - auto *via = Get(); - auto *reply = Get(); + auto fwd = Get(); + auto via = Get(); + auto reply = Get(); int top = marginTop(); QRect r(left, top, width, height - top - marginBottom()); @@ -7007,12 +7629,12 @@ void HistoryMessage::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 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()->lnk; - return; + result.link = author()->openLink(); + return result; } 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; + result.link = via->_lnk; + return result; } } trect.setTop(trect.top() + st::msgNameFont->height); @@ -7020,22 +7642,29 @@ void HistoryMessage::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 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(); + auto textRequest = request.forText(); if (breakEverywhere) { - state = HistoryInForwardedCursorState; + textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere; } - return; + textstyleSet(&st::inFwdTextStyle); + result = fwd->_text.getState(x - trect.left(), y - trect.top(), trect.width(), textRequest); + textstyleRestore(); + result.symbol = 0; + result.afterSymbol = false; + if (breakEverywhere) { + result.cursor = HistoryInForwardedCursorState; + } else { + result.cursor = HistoryDefaultCursorState; + } + return result; } 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; + result.link = via->_lnk; + return result; } trect.setTop(trect.top() + st::msgNameFont->height); } @@ -7043,85 +7672,59 @@ void HistoryMessage::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 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(); + result.link = reply->replyToLink(); } - return; + return result; } trect.setTop(trect.top() + h); } - bool inDate = false; - - TextLinkPtr medialnk; - 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 { + bool inDate = false, mediaDisplayed = _media && _media->isDisplayed(); + if (!mediaDisplayed || !_media->customInfoLayout()) { inDate = HistoryMessage::pointInTime(r.x() + r.width(), r.y() + r.height(), x, y, InfoDisplayDefault); } - textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); - bool inText = false; - _text.getState(lnk, inText, x - trect.x(), y - trect.y(), trect.width()); - textstyleRestore(); - + if (mediaDisplayed) { + trect.setBottom(trect.bottom() - _media->height()); + if (y >= r.bottom() - _media->height()) { + result = _media->getState(x - r.left(), y - (r.bottom() - _media->height()), request); + result.symbol += _text.length(); + } + } + if (!mediaDisplayed || (y < r.bottom() - _media->height())) { + textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); + result = _text.getState(x - trect.x(), y - trect.y(), trect.width(), request.forText()); + textstyleRestore(); + } if (inDate) { - state = HistoryInDateCursorState; - } else if (inText) { - state = HistoryInTextCursorState; - } else { - state = HistoryDefaultCursorState; + result.cursor = HistoryInDateCursorState; } } else { - _media->getState(lnk, state, x - left, y - marginTop(), this); + result = _media->getState(x - left, y - marginTop(), request); + result.symbol += _text.length(); } + + if (keyboard) { + int top = height + st::msgBotKbButton.margin - marginBottom(); + if (x >= left && x < left + width && y >= top && y < _height - marginBottom()) { + result.link = keyboard->getState(x - left, y - top); + return result; + } + } + + return result; } -void HistoryMessage::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const { - symbol = 0; - after = false; - upon = false; - - if (drawBubble()) { - int left = 0, width = 0, height = _height; - countPositionAndSize(left, width); - if (width < 1) return; - - 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() && !isPost()) ? st::outTextStyle : st::inTextStyle)); - _text.getSymbol(symbol, after, upon, x - trect.x(), y - trect.y(), trect.width()); - textstyleRestore(); +TextSelection HistoryMessage::adjustSelection(TextSelection selection, TextSelectType type) const { + if (!_media || selection.to <= _text.length()) { + return _text.adjustSelection(selection, type); } + auto mediaSelection = _media->adjustSelection(toMediaSelection(selection), type); + if (selection.from >= _text.length()) { + return fromMediaSelection(mediaSelection); + } + auto textSelection = _text.adjustSelection(selection, type); + return { textSelection.from, fromMediaSelection(mediaSelection).to }; } void HistoryMessage::drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const { @@ -7165,27 +7768,21 @@ bool HistoryMessage::hasFromPhoto() const { } HistoryMessage::~HistoryMessage() { - if (_media) { - _media->detachFromItem(this); - deleteAndMark(_media); - } - if (auto *reply = Get()) { + _media.clear(); + if (auto reply = Get()) { reply->clearData(this); } - if (_flags & MTPDmessage::Flag::f_reply_markup) { - App::clearReplyMarkup(channelId(), id); - } } void HistoryService::setMessageByAction(const MTPmessageAction &action) { - QList links; + QList links; LangString text = lang(lng_message_empty); QString from = textcmdLink(1, _from->name); switch (action.type()) { case mtpc_messageActionChatAddUser: { - const MTPDmessageActionChatAddUser &d(action.c_messageActionChatAddUser()); - const QVector &v(d.vusers.c_vector().v); + const auto &d(action.c_messageActionChatAddUser()); + const auto &v(d.vusers.c_vector().v); bool foundSelf = false; for (int32 i = 0, l = v.size(); i < l; ++i) { if (v.at(i).v == MTP::authedId()) { @@ -7198,7 +7795,7 @@ void HistoryService::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()) { @@ -7214,7 +7811,7 @@ void HistoryService::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); } @@ -7226,26 +7823,26 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } break; case mtpc_messageActionChatJoinedByLink: { - const MTPDmessageActionChatJoinedByLink &d(action.c_messageActionChatJoinedByLink()); - if (true || peerFromUser(d.vinviter_id) == _from->id) { + const auto &d(action.c_messageActionChatJoinedByLink()); + //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; } } break; case mtpc_messageActionChatCreate: { - const MTPDmessageActionChatCreate &d(action.c_messageActionChatCreate()); + const auto &d(action.c_messageActionChatCreate()); text = lng_action_created_chat(lt_from, from, lt_title, textClean(qs(d.vtitle))); } break; case mtpc_messageActionChannelCreate: { - const MTPDmessageActionChannelCreate &d(action.c_messageActionChannelCreate()); + const auto &d(action.c_messageActionChannelCreate()); if (isPost()) { text = lng_action_created_channel(lt_title, textClean(qs(d.vtitle))); } else { @@ -7258,33 +7855,32 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } break; case mtpc_messageActionChatDeleteUser: { - const MTPDmessageActionChatDeleteUser &d(action.c_messageActionChatDeleteUser()); + const auto &d(action.c_messageActionChatDeleteUser()); if (peerFromUser(d.vuser_id) == _from->id) { 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; case mtpc_messageActionChatEditPhoto: { - const MTPDmessageActionChatEditPhoto &d(action.c_messageActionChatEditPhoto()); + const auto &d(action.c_messageActionChatEditPhoto()); if (d.vphoto.type() == mtpc_photo) { - _media = new HistoryPhoto(history()->peer, d.vphoto.c_photo(), st::msgServicePhotoWidth); - _media->attachToItem(this); + _media.reset(new HistoryPhoto(this, history()->peer, d.vphoto.c_photo(), st::msgServicePhotoWidth)); } 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()); + const auto &d(action.c_messageActionChatEditTitle()); 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_ClientFlag::f_is_group_migrate; - const MTPDmessageActionChatMigrateTo &d(action.c_messageActionChatMigrateTo()); + const auto &d(action.c_messageActionChatMigrateTo()); if (true/*PeerData *channel = App::channelLoaded(d.vchannel_id.v)*/) { text = lang(lng_action_group_migrate); } else { @@ -7294,7 +7890,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { case mtpc_messageActionChannelMigrateFrom: { _flags |= MTPDmessage_ClientFlag::f_is_group_migrate; - const MTPDmessageActionChannelMigrateFrom &d(action.c_messageActionChannelMigrateFrom()); + const auto &d(action.c_messageActionChannelMigrateFrom()); if (true/*PeerData *chat = App::chatLoaded(d.vchat_id.v)*/) { text = lang(lng_action_group_migrate); } else { @@ -7304,7 +7900,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { case mtpc_messageActionPinMessage: { if (updatePinnedText(&from, &text)) { - auto *pinned = Get(); + auto pinned = Get(); t_assert(pinned != nullptr); links.push_back(pinned->lnk); @@ -7318,7 +7914,7 @@ void HistoryService::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)); @@ -7326,7 +7922,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } bool HistoryService::updatePinned(bool force) { - auto *pinned = Get(); + auto pinned = Get(); t_assert(pinned != nullptr); if (!force) { @@ -7336,7 +7932,7 @@ bool HistoryService::updatePinned(bool force) { } if (!pinned->lnk) { - pinned->lnk = TextLinkPtr(new MessageLink(history()->peer->id, pinned->msgId)); + pinned->lnk.reset(new GoToMessageClickHandler(history()->peer->id, pinned->msgId)); } bool gotDependencyItem = false; if (!pinned->msg) { @@ -7373,8 +7969,8 @@ bool HistoryService::updatePinnedText(const QString *pfrom, QString *ptext) { from = textcmdLink(1, _from->name); } - TextLinkPtr second; - auto *pinned = Get(); + ClickHandlerPtr second; + auto pinned = Get(); if (pinned && pinned->msg) { HistoryMedia *media = pinned->msg->getMedia(); QString mediaText; @@ -7421,7 +8017,7 @@ bool HistoryService::updatePinnedText(const QString *pfrom, QString *ptext) { *ptext = text; } else { setServiceText(text); - _text.setLink(1, TextLinkPtr(new PeerLink(_from))); + _text.setLink(1, MakeShared(_from)); if (second) { _text.setLink(2, second); } @@ -7448,16 +8044,15 @@ HistoryService::HistoryService(History *history, const MTPDmessageService &msg) setMessageByAction(msg.vaction); } -HistoryService::HistoryService(History *history, MsgId msgId, QDateTime date, const QString &msg, MTPDmessage::Flags flags, HistoryMedia *media, int32 from) : - HistoryItem(history, 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 HistoryService::initDimensions() { _maxw = _text.maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right(); _minh = _text.minHeight(); - if (_media) _media->initDimensions(this); + if (_media) _media->initDimensions(); } void HistoryService::countPositionAndSize(int32 &left, int32 &width) const { @@ -7469,14 +8064,12 @@ void HistoryService::countPositionAndSize(int32 &left, int32 &width) const { 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 HistoryService::selectedText(TextSelection selection) const { + return _text.original((selection == FullSelection) ? AllTextSelection : selection); } QString HistoryService::inDialogsText() const { - return _text.original(0, 0xFFFF, Text::ExpandLinksNone); + return _text.original(AllTextSelection, Text::ExpandLinksNone); } QString HistoryService::inReplyText() const { @@ -7491,21 +8084,25 @@ void HistoryService::setServiceText(const QString &text) { initDimensions(); } -void HistoryService::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { +void HistoryService::draw(Painter &p, const QRect &r, TextSelection 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()) { + if (auto date = Get()) { dateh = date->height(); - date->paint(p, 0, _history->width); + if (r.intersects(QRect(0, 0, _history->width, dateh))) { + date->paint(p, 0, _history->width); + } p.translate(0, dateh); height -= dateh; } - if (auto *unreadbar = Get()) { + if (auto unreadbar = Get()) { unreadbarh = unreadbar->height(); - unreadbar->paint(p, 0, _history->width); + if (r.intersects(QRect(0, 0, _history->width, unreadbarh))) { + unreadbar->paint(p, 0, _history->width); + } p.translate(0, unreadbarh); height -= unreadbarh; } @@ -7531,7 +8128,7 @@ void HistoryService::draw(Painter &p, const QRect &r, uint32 selection, uint64 m height -= st::msgServiceMargin.top() + _media->height(); 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); + _media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms); p.translate(-left, -top); } @@ -7548,9 +8145,7 @@ void HistoryService::draw(Painter &p, const QRect &r, uint32 selection, uint64 m p.setBrush(Qt::NoBrush); 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); + _text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, selection); textstyleRestore(); @@ -7582,16 +8177,16 @@ int32 HistoryService::resizeGetHeight_(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()); } _height += displayedDateHeight(); - if (auto *unreadbar = Get()) { + if (auto unreadbar = Get()) { _height += unreadbar->height(); } return _height; } -bool HistoryService::hasPoint(int32 x, int32 y) const { +bool HistoryService::hasPoint(int x, int 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; @@ -7600,7 +8195,7 @@ bool HistoryService::hasPoint(int32 x, int32 y) const { y -= dateh; height -= dateh; } - if (auto *unreadbar = Get()) { + if (auto unreadbar = Get()) { int unreadbarh = unreadbar->height(); y -= unreadbarh; height -= unreadbarh; @@ -7612,19 +8207,18 @@ bool HistoryService::hasPoint(int32 x, int32 y) const { return QRect(left, st::msgServiceMargin.top(), width, height).contains(x, y); } -void HistoryService::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { - lnk = TextLinkPtr(); - state = HistoryDefaultCursorState; +HistoryTextState HistoryService::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; int left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins countPositionAndSize(left, width); - if (width < 1) return; + if (width < 1) return result; if (int dateh = displayedDateHeight()) { y -= dateh; height -= dateh; } - if (auto *unreadbar = Get()) { + if (auto unreadbar = Get()) { int unreadbarh = unreadbar->height(); y -= unreadbarh; height -= unreadbarh; @@ -7636,41 +8230,14 @@ void HistoryService::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); if (trect.contains(x, y)) { textstyleSet(&st::serviceTextStyle); - bool inText = false; - _text.getState(lnk, inText, x - trect.x(), y - trect.y(), trect.width(), Qt::AlignCenter); + auto textRequest = request.forText(); + textRequest.align = style::al_center; + result = _text.getState(x - trect.x(), y - trect.y(), trect.width(), textRequest); textstyleRestore(); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; } else if (_media) { - _media->getState(lnk, state, x - st::msgServiceMargin.left() - (width - _media->maxWidth()) / 2, y - st::msgServiceMargin.top() - height - st::msgServiceMargin.top(), this); + result = _media->getState(x - st::msgServiceMargin.left() - (width - _media->maxWidth()) / 2, y - st::msgServiceMargin.top() - height - st::msgServiceMargin.top(), request); } -} - -void HistoryService::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const { - symbol = 0; - after = false; - upon = false; - - 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(); - } - QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); - textstyleSet(&st::serviceTextStyle); - _text.getSymbol(symbol, after, upon, x - trect.x(), y - trect.y(), trect.width(), Qt::AlignCenter); - textstyleRestore(); + return result; } void HistoryService::drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const { @@ -7689,8 +8256,8 @@ QString HistoryService::notificationText() const { return msg; } -HistoryMedia *HistoryService::getMedia(bool inOverview) const { - return inOverview ? 0 : _media; +HistoryMedia *HistoryService::getMedia() const { + return _media.data(); } HistoryService::~HistoryService() { @@ -7699,10 +8266,7 @@ HistoryService::~HistoryService() { App::historyUnregDependency(this, pinned->msg); } } - if (_media) { - _media->detachFromItem(this); - deleteAndMark(_media); - } + _media.clear(); } HistoryGroup::HistoryGroup(History *history, const MTPDmessageGroup &group, const QDateTime &date) @@ -7710,7 +8274,7 @@ HistoryGroup::HistoryGroup(History *history, const MTPDmessageGroup &group, cons , _minId(group.vmin_id.v) , _maxId(group.vmax_id.v) , _count(group.vcount.v) - , _lnk(new CommentsLink(this)) { + , _lnk(new CommentsClickHandler(this)) { } HistoryGroup::HistoryGroup(History *history, HistoryItem *newItem, const QDateTime &date) @@ -7718,16 +8282,15 @@ HistoryGroup::HistoryGroup(History *history, HistoryItem *newItem, const QDateTi , _minId(newItem->id - 1) , _maxId(newItem->id + 1) , _count(1) - , _lnk(new CommentsLink(this)) { + , _lnk(new CommentsClickHandler(this)) { } -void HistoryGroup::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { - lnk = TextLinkPtr(); - state = HistoryDefaultCursorState; +HistoryTextState HistoryGroup::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; int32 left = 0, width = 0, height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); // two small margins countPositionAndSize(left, width); - if (width < 1) return; + if (width < 1) return result; QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); if (width > _maxw) { @@ -7735,8 +8298,9 @@ void HistoryGroup::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x width = _maxw; } if (QRect(left, st::msgServiceMargin.top(), width, height).contains(x, y)) { - lnk = _lnk; + result.link = _lnk; } + return result; } void HistoryGroup::uniteWith(MsgId minId, MsgId maxId, int32 count) { @@ -7788,12 +8352,11 @@ HistoryCollapse::HistoryCollapse(History *history, MsgId wasMinId, const QDateTi , _wasMinId(wasMinId) { } -void HistoryCollapse::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { +void HistoryCollapse::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { } -void HistoryCollapse::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { - lnk = TextLinkPtr(); - state = HistoryDefaultCursorState; +HistoryTextState HistoryCollapse::getState(int x, int y, HistoryStateRequest request) const { + return HistoryTextState(); } HistoryJoined::HistoryJoined(History *history, const QDateTime &inviteDate, UserData *inviter, MTPDmessage::Flags flags) @@ -7803,7 +8366,23 @@ HistoryJoined::HistoryJoined(History *history, const QDateTime &inviteDate, User _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(); } + +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 CommentsClickHandler::onClickImpl() const { + if (App::main() && peerIsChannel(peer())) { + Ui::showPeerHistory(peer(), msgid()); + } +} diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 497dd1060..d60c855f6 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -27,6 +27,7 @@ class HistoryItem; typedef QMap SelectedItemSet; #include "structs.h" +#include "dialogs/dialogs_common.h" enum NewMessageType { NewMessageUnread, @@ -64,6 +65,9 @@ public: int32 unreadBadge() const { return _unreadFull - (cIncludeMuted() ? 0 : _unreadMuted); } + int32 unreadMutedCount() const { + return _unreadMuted; + } bool unreadOnlyMuted() const { return cIncludeMuted() ? (_unreadMuted >= _unreadFull) : false; } @@ -88,29 +92,6 @@ private: class HistoryBlock; -struct DialogRow { - DialogRow(History *history = 0, DialogRow *prev = 0, DialogRow *next = 0, int32 pos = 0) : prev(prev), next(next), history(history), pos(pos), attached(0) { - } - - void paint(Painter &p, int32 w, bool act, bool sel, bool onlyBackground) const; - - DialogRow *prev, *next; - History *history; - int32 pos; - void *attached; // for any attached data, for example View in contacts list -}; - -struct FakeDialogRow { - FakeDialogRow(HistoryItem *item) : _item(item), _cacheFor(0), _cache(st::dlgRichMinWidth) { - } - - void paint(Painter &p, int32 w, bool act, bool sel, bool onlyBackground) const; - - HistoryItem *_item; - mutable const HistoryItem *_cacheFor; - mutable Text _cache; -}; - enum HistoryMediaType { MediaTypePhoto, MediaTypeVideo, @@ -145,6 +126,7 @@ inline MTPMessagesFilter typeToMediaFilter(MediaOverviewType &type) { case OverviewFiles: return MTP_inputMessagesFilterDocument(); case OverviewVoiceFiles: return MTP_inputMessagesFilterVoice(); case OverviewLinks: return MTP_inputMessagesFilterUrl(); + case OverviewCount: break; default: type = OverviewCount; break; } return MTPMessagesFilter(); @@ -214,7 +196,11 @@ enum AddToOverviewMethod { AddToOverviewBack, // when new messages slice was received and it is the last one, we index all media }; -struct DialogsIndexed; +namespace Dialogs { +class Row; +class IndexedList; +} // namespace Dialogs + class ChannelHistory; class History { public: @@ -242,12 +228,12 @@ public: virtual ~History(); - HistoryItem *addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags 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, 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); - HistoryItem *addNewPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption); + 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); @@ -266,7 +252,13 @@ public: HistoryItem *lastImportantMessage() const; + int unreadCount() const { + return _unreadCount; + } void setUnreadCount(int newUnreadCount, bool psUpdate = true); + bool mute() const { + return _mute; + } void setMute(bool newMute); void getNextShowFrom(HistoryBlock *block, int i); void addUnreadBar(); @@ -282,22 +274,23 @@ public: void setLastMessage(HistoryItem *msg); 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(); + struct PositionInChatListChange { + int movedFrom; + int movedTo; + }; + PositionInChatListChange adjustByPosInChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed); + bool inChatList(Dialogs::Mode list) const { + return !chatListLinks(list).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); + int posInChatList(Dialogs::Mode list) const; + Dialogs::Row *addToChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed); + void removeFromChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed); + void removeChatListEntryByLetter(Dialogs::Mode list, QChar letter); + void addChatListEntryByLetter(Dialogs::Mode list, QChar letter, Dialogs::Row *row); void updateChatListEntry() const; MsgId minMsgId() const; @@ -351,31 +344,52 @@ public: typedef QList Blocks; Blocks blocks; - int32 width, height, msgCount, unreadCount; - int32 inboxReadBefore, outboxReadBefore; - HistoryItem *showFrom; - HistoryItem *unreadBar; + int width = 0; + int height = 0; + int32 msgCount = 0; + MsgId inboxReadBefore = 1; + MsgId outboxReadBefore = 1; + HistoryItem *showFrom = nullptr; + HistoryItem *unreadBar = nullptr; PeerData *peer; - bool oldLoaded, newLoaded; - HistoryItem *lastMsg; + bool oldLoaded = false; + bool newLoaded = true; + HistoryItem *lastMsg = nullptr; QDateTime lastMsgDate; typedef QList NotifyQueue; NotifyQueue notifies; - HistoryDraft *msgDraft; - HistoryEditDraft *editDraft; + HistoryDraft *msgDraft() { + return _msgDraft.get(); + } + HistoryEditDraft *editDraft() { + return _editDraft.get(); + } + void setMsgDraft(std_::unique_ptr &&draft) { + _msgDraft = std_::move(draft); + } + void takeMsgDraft(History *from) { + if (auto &draft = from->_msgDraft) { + if (!draft->text.isEmpty() && !_msgDraft) { + _msgDraft = std_::move(draft); + _msgDraft->msgId = 0; // edit and reply to drafts can't migrate + } + from->clearMsgDraft(); + } + } + void setEditDraft(std_::unique_ptr &&draft) { + _editDraft = std_::move(draft); + } + void clearMsgDraft() { + _msgDraft = nullptr; + } + void clearEditDraft() { + _editDraft = nullptr; + } 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; + return _editDraft ? editDraft() : msgDraft(); } // some fields below are a property of a currently displayed instance of this @@ -383,13 +397,13 @@ public: public: // we save the last showAtMsgId to restore the state when switching // between different conversation histories - MsgId showAtMsgId; + MsgId showAtMsgId = ShowAtUnreadMsgId; // 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; + HistoryItem *scrollTopItem = nullptr; + int scrollTopOffset = 0; void forgetScrollState() { scrollTopItem = nullptr; } @@ -410,23 +424,23 @@ protected: public: - bool mute; + bool lastKeyboardInited = false; + bool lastKeyboardUsed = false; + MsgId lastKeyboardId = 0; + MsgId lastKeyboardHiddenId = 0; + PeerId lastKeyboardFrom = 0; - bool lastKeyboardInited, lastKeyboardUsed; - MsgId lastKeyboardId, lastKeyboardHiddenId; - PeerId lastKeyboardFrom; + mtpRequestId sendRequestId = 0; - mtpRequestId sendRequestId; - - mutable const HistoryItem *textCachedFor; // cache - mutable Text lastItemTextCache; + mutable const HistoryItem *textCachedFor = nullptr; // cache + mutable Text lastItemTextCache = Text{ int(st::dlgRichMinWidth) }; typedef QMap TypingUsers; TypingUsers typing; typedef QMap SendActionUsers; SendActionUsers sendActions; QString typingStr; - Text typingText; + Text typingText = Text{ int(st::dlgRichMinWidth) }; uint32 typingDots; QMap mySendActions; @@ -470,35 +484,7 @@ protected: void clearOnDestroy(); HistoryItem *addNewToLastBlock(const MTPMessage &msg, NewMessageType type); -private: - - enum 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) Q_DECL_NOTHROW { - return QFlags(f1) | f2; - } - Q_DECL_CONSTEXPR friend inline QFlags operator|(Flags::enum_type f1, QFlags f2) Q_DECL_NOTHROW { - return f2 | f1; - } - 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; // this method just removes a block from the blocks list // when the last item from this block was detached and @@ -509,16 +495,93 @@ private: 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); - HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption); + 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 *addItemToBlock(HistoryItem *item, HistoryBlock *block); HistoryItem *addNewItem(HistoryItem *adding, bool newMsg); - HistoryItem *addMessageGroupAfterPrevToBlock(const MTPDmessageGroup &group, HistoryItem *prev, HistoryBlock *block); HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex); - HistoryBlock *pushBackNewBlock(); - HistoryBlock *pushFrontNewBlock(); + // 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 != nullptr; + } + +private: + + // After adding a new history slice check the lastMsg and newLoaded. + void checkLastMsg(); + + 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; + bool _mute; + int32 _unreadCount = 0; + + Dialogs::RowsByLetter _chatListLinks[2]; + Dialogs::RowsByLetter &chatListLinks(Dialogs::Mode list) { + return _chatListLinks[static_cast(list)]; + } + const Dialogs::RowsByLetter &chatListLinks(Dialogs::Mode list) const { + return _chatListLinks[static_cast(list)]; + } + Dialogs::Row *mainChatListLink(Dialogs::Mode list) const { + auto it = chatListLinks(list).constFind(0); + t_assert(it != chatListLinks(list).cend()); + return it.value(); + } + uint64 _sortKeyInChatList = 0; // 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 + + // 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; + }; + std_::unique_ptr _buildingFrontBlock; + + // Creates if necessary a new block for adding item. + // Depending on isBuildingFrontBlock() gets front or back block. + HistoryBlock *prepareBlockForAddingItem(); + + std_::unique_ptr _msgDraft; + std_::unique_ptr _editDraft; }; @@ -598,280 +661,6 @@ private: }; -enum DialogsSortMode { - DialogsSortByDate, - DialogsSortByName, - DialogsSortByAdd -}; - -struct DialogsList { - DialogsList(DialogsSortMode sortMode) : begin(&last), end(&last), sortMode(sortMode), count(0), current(&last) { - } - - void adjustCurrent(int32 y, int32 h) const { - int32 pos = (y > 0) ? (y / h) : 0; - while (current->pos > pos && current != begin) { - current = current->prev; - } - while (current->pos + 1 <= pos && current->next != end) { - current = current->next; - } - } - - void paint(Painter &p, int32 w, int32 hFrom, int32 hTo, PeerData *act, PeerData *sel, bool onlyBackground) const { - adjustCurrent(hFrom, st::dlgHeight); - - DialogRow *drawFrom = current; - p.translate(0, drawFrom->pos * st::dlgHeight); - while (drawFrom != end && drawFrom->pos * st::dlgHeight < hTo) { - bool active = (drawFrom->history->peer == act) || (drawFrom->history->peer->migrateTo() && drawFrom->history->peer->migrateTo() == act); - bool selected = (drawFrom->history->peer == sel); - drawFrom->paint(p, w, active, selected, onlyBackground); - drawFrom = drawFrom->next; - p.translate(0, st::dlgHeight); - } - } - - DialogRow *rowAtY(int32 y, int32 h) const { - if (!count) return 0; - - int32 pos = (y > 0) ? (y / h) : 0; - adjustCurrent(y, h); - return (pos == current->pos) ? current : 0; - } - - DialogRow *addToEnd(History *history) { - DialogRow *result = new DialogRow(history, end->prev, end, end->pos); - end->pos++; - if (begin == end) { - begin = current = result; - } else { - end->prev->next = result; - } - rowByPeer.insert(history->peer->id, result); - ++count; - end->prev = result; - if (sortMode == DialogsSortByDate) { - adjustByPos(result); - } - return result; - } - - bool insertBefore(DialogRow *row, DialogRow *before) { - if (row == before) return false; - - if (current == row) current = row->prev; - - DialogRow *updateTill = row->prev; - remove(row); - - // insert row - row->next = before; // update row - row->prev = before->prev; - row->next->prev = row; // update row->next - if (row->prev) { // update row->prev - row->prev->next = row; - } else { - begin = row; - } - - // update y - for (DialogRow *n = row; n != updateTill; n = n->next) { - n->next->pos++; - row->pos--; - } - return true; - } - - bool insertAfter(DialogRow *row, DialogRow *after) { - if (row == after) return false; - - if (current == row) current = row->next; - - DialogRow *updateFrom = row->next; - remove(row); - - // insert row - row->prev = after; // update row - row->next = after->next; - row->prev->next = row; // update row->prev - row->next->prev = row; // update row->next - - // update y - for (DialogRow *n = updateFrom; n != row; n = n->next) { - n->pos--; - row->pos++; - } - return true; - } - - DialogRow *adjustByName(const PeerData *peer) { - if (sortMode != DialogsSortByName) return 0; - - RowByPeer::iterator i = rowByPeer.find(peer->id); - if (i == rowByPeer.cend()) return 0; - - DialogRow *row = i.value(), *change = row; - while (change->prev && change->prev->history->peer->name > peer->name) { - change = change->prev; - } - if (!insertBefore(row, change)) { - while (change->next != end && change->next->history->peer->name < peer->name) { - change = change->next; - } - insertAfter(row, change); - } - return row; - } - - DialogRow *addByName(History *history) { - if (sortMode != DialogsSortByName) return 0; - - DialogRow *row = addToEnd(history), *change = row; - const QString &peerName(history->peer->name); - while (change->prev && change->prev->history->peer->name.compare(peerName, Qt::CaseInsensitive) > 0) { - change = change->prev; - } - if (!insertBefore(row, change)) { - while (change->next != end && change->next->history->peer->name.compare(peerName, Qt::CaseInsensitive) < 0) { - change = change->next; - } - insertAfter(row, change); - } - return row; - } - - void adjustByPos(DialogRow *row) { - if (sortMode != DialogsSortByDate) return; - - DialogRow *change = row; - if (change != begin && begin->history->sortKeyInChatList() < row->history->sortKeyInChatList()) { - change = begin; - } 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->sortKeyInChatList() > row->history->sortKeyInChatList()) { - change = end->prev; - } else while (change->next != end && change->next->history->sortKeyInChatList() > row->history->sortKeyInChatList()) { - change = change->next; - } - insertAfter(row, change); - } - } - - bool del(const PeerId &peerId, DialogRow *replacedBy = 0); - - void remove(DialogRow *row) { - row->next->prev = row->prev; // update row->next - if (row->prev) { // update row->prev - row->prev->next = row->next; - } else { - begin = row->next; - } - } - - void clear() { - while (begin != end) { - current = begin; - begin = begin->next; - delete current; - } - current = begin; - rowByPeer.clear(); - count = 0; - } - - ~DialogsList() { - clear(); - } - - DialogRow last; - DialogRow *begin, *end; - DialogsSortMode sortMode; - int32 count; - - typedef QHash RowByPeer; - RowByPeer rowByPeer; - - mutable DialogRow *current; // cache -}; - -struct DialogsIndexed { - DialogsIndexed(DialogsSortMode sortMode) : sortMode(sortMode), list(sortMode) { - } - - History::ChatListLinksMap addToEnd(History *history) { - History::ChatListLinksMap result; - DialogsList::RowByPeer::const_iterator i = list.rowByPeer.find(history->peer->id); - 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)); - } - } - return result; - } - - DialogRow *addByName(History *history) { - DialogsList::RowByPeer::const_iterator i = list.rowByPeer.constFind(history->peer->id); - if (i != list.rowByPeer.cend()) { - return i.value(); - } - - DialogRow *res = list.addByName(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)); - } - j.value()->addByName(history); - } - return res; - } - - 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 { - DialogsIndex::iterator j = index.find(i.key()); - if (j != index.cend()) { - j.value()->adjustByPos(i.value()); - } - } - } - } - - void peerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); - - void del(const PeerData *peer, DialogRow *replacedBy = 0) { - if (list.del(peer->id, replacedBy)) { - for (PeerData::NameFirstChars::const_iterator i = peer->chars.cbegin(), e = peer->chars.cend(); i != e; ++i) { - DialogsIndex::iterator j = index.find(*i); - if (j != index.cend()) { - j.value()->del(peer->id, replacedBy); - } - } - } - } - - ~DialogsIndexed() { - clear(); - } - - void clear(); - - DialogsSortMode sortMode; - DialogsList list; - typedef QMap DialogsIndex; - DialogsIndex index; -}; - class HistoryBlock { public: HistoryBlock(History *hist) : y(0), height(0), history(hist), _indexInHistory(-1) { @@ -940,7 +729,7 @@ protected: }; -class HistoryMessage; // dynamic_cast optimize +class HistoryMessage; enum HistoryCursorState { HistoryDefaultCursorState, @@ -949,9 +738,40 @@ enum HistoryCursorState { HistoryInForwardedCursorState, }; +struct HistoryTextState { + HistoryTextState() = default; + HistoryTextState(const Text::StateResult &state) + : cursor(state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState) + , link(state.link) + , afterSymbol(state.afterSymbol) + , symbol(state.symbol) { + } + HistoryTextState &operator=(const Text::StateResult &state) { + cursor = state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState; + link = state.link; + afterSymbol = state.afterSymbol; + symbol = state.symbol; + return *this; + } + HistoryCursorState cursor = HistoryDefaultCursorState; + ClickHandlerPtr link; + bool afterSymbol = false; + uint16 symbol = 0; +}; + +struct HistoryStateRequest { + Text::StateRequest::Flags flags = Text::StateRequest::Flag::LookupLink; + Text::StateRequest forText() const { + Text::StateRequest result; + result.flags = flags; + return result; + } +}; + enum InfoDisplayType { InfoDisplayDefault, InfoDisplayOverImage, + InfoDisplayOverBackground, }; inline bool isImportantChannelMessage(MsgId id, MTPDmessage::Flags flags) { // client-side important msgs always has_views or has_from_id @@ -966,8 +786,6 @@ enum HistoryItemType { }; struct HistoryMessageVia : public BaseComponent { - HistoryMessageVia(Composer*) { - } void create(int32 userId); void resize(int32 availw) const; @@ -975,22 +793,16 @@ struct HistoryMessageVia : public BaseComponent { mutable QString _text; mutable int _width = 0; mutable int _maxWidth = 0; - TextLinkPtr _lnk; + ClickHandlerPtr _lnk; }; struct HistoryMessageViews : public BaseComponent { - HistoryMessageViews(Composer*) { - } - QString _viewsText; int _views = 0; int _viewsWidth = 0; }; struct HistoryMessageSigned : public BaseComponent { - HistoryMessageSigned(Composer*) { - } - void create(UserData *from, const QDateTime &date); int maxWidth() const; @@ -998,8 +810,6 @@ struct HistoryMessageSigned : public BaseComponent { }; struct HistoryMessageForwarded : public BaseComponent { - HistoryMessageForwarded(Composer*) { - } void create(const HistoryMessageVia *via) const; PeerData *_authorOriginal = nullptr; @@ -1009,17 +819,15 @@ struct HistoryMessageForwarded : public BaseComponent { }; struct HistoryMessageReply : public BaseComponent { - HistoryMessageReply(Composer*) { - } HistoryMessageReply &operator=(HistoryMessageReply &&other) { replyToMsgId = other.replyToMsgId; std::swap(replyToMsg, other.replyToMsg); - replyToLnk = std11::move(other.replyToLnk); - replyToName = std11::move(other.replyToName); - replyToText = std11::move(other.replyToText); + replyToLnk = std_::move(other.replyToLnk); + replyToName = std_::move(other.replyToName); + replyToText = std_::move(other.replyToText); replyToVersion = other.replyToVersion; _maxReplyWidth = other._maxReplyWidth; - std::swap(_replyToVia, other._replyToVia); + _replyToVia = std_::move(other._replyToVia); return *this; } ~HistoryMessageReply() { @@ -1027,6 +835,7 @@ struct HistoryMessageReply : public BaseComponent { t_assert(replyToMsg == nullptr); t_assert(_replyToVia == nullptr); } + bool updateData(HistoryMessage *holder, bool force = false); void clearData(HistoryMessage *holder); // must be called before destructor @@ -1048,22 +857,149 @@ struct HistoryMessageReply : public BaseComponent { int replyToWidth() const { return _maxReplyWidth; } - TextLinkPtr replyToLink() const { + ClickHandlerPtr replyToLink() const { return replyToLnk; } MsgId replyToMsgId = 0; HistoryItem *replyToMsg = nullptr; - TextLinkPtr replyToLnk; + ClickHandlerPtr replyToLnk; mutable Text replyToName, replyToText; mutable int replyToVersion = 0; mutable int _maxReplyWidth = 0; - HistoryMessageVia *_replyToVia = nullptr; + std_::unique_ptr _replyToVia; int toWidth = 0; }; Q_DECLARE_OPERATORS_FOR_FLAGS(HistoryMessageReply::PaintFlags); -class HistoryDependentItemCallback : public SharedCallback2 { +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